use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use crate::error::{AugentError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MergeStrategy {
#[default]
Replace,
Shallow,
Deep,
Composite,
}
impl MergeStrategy {
#[allow(dead_code)] pub fn merge_strings(&self, existing: &str, new_content: &str) -> Result<String> {
match self {
MergeStrategy::Replace => Ok(new_content.to_string()),
MergeStrategy::Composite => Ok(merge_composite(existing, new_content)),
MergeStrategy::Shallow | MergeStrategy::Deep => {
let existing_json: JsonValue =
serde_json::from_str(existing).map_err(|e| AugentError::ConfigParseFailed {
path: "merge source".to_string(),
reason: e.to_string(),
})?;
let new_json: JsonValue = serde_json::from_str(new_content).map_err(|e| {
AugentError::ConfigParseFailed {
path: "merge target".to_string(),
reason: e.to_string(),
}
})?;
let merged = match self {
MergeStrategy::Shallow => merge_json_shallow(existing_json, new_json),
MergeStrategy::Deep => merge_json_deep(existing_json, new_json),
_ => unreachable!(),
};
serde_json::to_string_pretty(&merged).map_err(|e| AugentError::ConfigParseFailed {
path: "merge result".to_string(),
reason: e.to_string(),
})
}
}
}
}
#[allow(dead_code)] fn merge_composite(existing: &str, new_content: &str) -> String {
let existing = existing.trim();
let new_content = new_content.trim();
if existing.is_empty() {
return new_content.to_string();
}
if new_content.is_empty() {
return existing.to_string();
}
format!(
"{}\n\n<!-- Augent: Additional content below -->\n\n{}",
existing, new_content
)
}
#[allow(dead_code)] fn merge_json_shallow(mut existing: JsonValue, new: JsonValue) -> JsonValue {
if let (JsonValue::Object(existing_map), JsonValue::Object(new_map)) = (&mut existing, new) {
for (key, value) in new_map {
existing_map.insert(key, value);
}
}
existing
}
#[allow(dead_code)] fn merge_json_deep(existing: JsonValue, new: JsonValue) -> JsonValue {
match (existing, new) {
(JsonValue::Object(mut existing_map), JsonValue::Object(new_map)) => {
for (key, new_value) in new_map {
let merged_value = match existing_map.remove(&key) {
Some(existing_value) => merge_json_deep(existing_value, new_value),
None => new_value,
};
existing_map.insert(key, merged_value);
}
JsonValue::Object(existing_map)
}
(JsonValue::Array(mut existing_arr), JsonValue::Array(new_arr)) => {
for item in new_arr {
if !existing_arr.contains(&item) {
existing_arr.push(item);
}
}
JsonValue::Array(existing_arr)
}
(_, new) => new,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_replace() {
let result = MergeStrategy::Replace
.merge_strings("old content", "new content")
.unwrap();
assert_eq!(result, "new content");
}
#[test]
fn test_merge_composite() {
let result = MergeStrategy::Composite
.merge_strings("# Existing\nContent here", "# New\nMore content")
.unwrap();
assert!(result.contains("# Existing"));
assert!(result.contains("# New"));
assert!(result.contains("<!-- Augent:"));
}
#[test]
fn test_merge_composite_empty_existing() {
let result = MergeStrategy::Composite
.merge_strings("", "new content")
.unwrap();
assert_eq!(result, "new content");
}
#[test]
fn test_merge_composite_empty_new() {
let result = MergeStrategy::Composite
.merge_strings("existing", "")
.unwrap();
assert_eq!(result, "existing");
}
#[test]
fn test_merge_shallow() {
let existing = r#"{"a": 1, "b": {"x": 1}}"#;
let new = r#"{"b": {"y": 2}, "c": 3}"#;
let result = MergeStrategy::Shallow.merge_strings(existing, new).unwrap();
let parsed: JsonValue = serde_json::from_str(&result).unwrap();
assert_eq!(parsed["a"], 1);
assert_eq!(parsed["c"], 3);
assert_eq!(parsed["b"]["y"], 2);
assert!(parsed["b"]["x"].is_null());
}
#[test]
fn test_merge_deep() {
let existing = r#"{"a": 1, "b": {"x": 1, "y": 2}}"#;
let new = r#"{"b": {"y": 3, "z": 4}, "c": 5}"#;
let result = MergeStrategy::Deep.merge_strings(existing, new).unwrap();
let parsed: JsonValue = serde_json::from_str(&result).unwrap();
assert_eq!(parsed["a"], 1);
assert_eq!(parsed["c"], 5);
assert_eq!(parsed["b"]["x"], 1);
assert_eq!(parsed["b"]["y"], 3);
assert_eq!(parsed["b"]["z"], 4);
}
#[test]
fn test_merge_deep_arrays() {
let existing = r#"{"arr": [1, 2]}"#;
let new = r#"{"arr": [2, 3]}"#;
let result = MergeStrategy::Deep.merge_strings(existing, new).unwrap();
let parsed: JsonValue = serde_json::from_str(&result).unwrap();
let arr = parsed["arr"].as_array().unwrap();
assert!(arr.contains(&JsonValue::from(1)));
assert!(arr.contains(&JsonValue::from(2)));
assert!(arr.contains(&JsonValue::from(3)));
}
#[test]
fn test_merge_json_invalid() {
let result = MergeStrategy::Deep.merge_strings("not json", "{}");
assert!(result.is_err());
}
#[test]
fn test_merge_shallow_with_arrays() {
let existing = r#"{"arr": [1, 2, 3]}"#;
let new = r#"{"arr": [4, 5, 6]}"#;
let result = MergeStrategy::Shallow.merge_strings(existing, new).unwrap();
let parsed: JsonValue = serde_json::from_str(&result).unwrap();
let arr = parsed["arr"].as_array().unwrap();
assert_eq!(arr.len(), 3);
assert!(arr.contains(&JsonValue::from(4)));
assert!(arr.contains(&JsonValue::from(5)));
assert!(arr.contains(&JsonValue::from(6)));
assert!(!arr.contains(&JsonValue::from(1)));
}
#[test]
fn test_merge_shallow_with_null() {
let existing = r#"{"a": 1, "b": null}"#;
let new = r#"{"b": 2, "c": null}"#;
let result = MergeStrategy::Shallow.merge_strings(existing, new).unwrap();
let parsed: JsonValue = serde_json::from_str(&result).unwrap();
assert_eq!(parsed["a"], 1);
assert_eq!(parsed["b"], 2);
assert!(parsed["c"].is_null());
}
#[test]
fn test_merge_deep_with_complex_nesting() {
let existing = r#"{
"level1": {
"level2": {
"level3": {
"a": 1,
"b": 2
},
"other": "keep"
}
}
}"#;
let new = r#"{
"level1": {
"level2": {
"level3": {
"b": 20,
"c": 3
},
"another": "add"
}
}
}"#;
let result = MergeStrategy::Deep.merge_strings(existing, new).unwrap();
let parsed: JsonValue = serde_json::from_str(&result).unwrap();
assert_eq!(parsed["level1"]["level2"]["level3"]["a"], 1);
assert_eq!(parsed["level1"]["level2"]["level3"]["b"], 20);
assert_eq!(parsed["level1"]["level2"]["level3"]["c"], 3);
assert_eq!(parsed["level1"]["level2"]["other"], "keep");
assert_eq!(parsed["level1"]["level2"]["another"], "add");
}
#[test]
fn test_merge_deep_with_object_arrays() {
let existing = r#"{"items": [{"id": 1, "name": "one"}]}"#;
let new = r#"{"items": [{"id": 2, "name": "two"}]}"#;
let result = MergeStrategy::Deep.merge_strings(existing, new).unwrap();
let parsed: JsonValue = serde_json::from_str(&result).unwrap();
let arr = parsed["items"].as_array().unwrap();
assert_eq!(arr.len(), 2);
}
#[test]
fn test_merge_composite_whitespace_handling() {
let existing = "# Title\n\n Some content \n";
let new = " \n# Another\n\n More content \n ";
let result = MergeStrategy::Composite
.merge_strings(existing, new)
.unwrap();
assert!(result.contains("# Title"));
assert!(result.contains("Some content"));
assert!(result.contains("# Another"));
assert!(result.contains("More content"));
assert!(result.contains("<!-- Augent:"));
}
#[test]
fn test_merge_composite_multiple_merges() {
let first = "# First bundle\nContent 1";
let second = "# Second bundle\nContent 2";
let third = "# Third bundle\nContent 3";
let result1 = MergeStrategy::Composite.merge_strings("", first).unwrap();
assert!(result1.contains("First bundle"));
assert!(result1.contains("Content 1"));
let result2 = MergeStrategy::Composite
.merge_strings(&result1, second)
.unwrap();
assert!(result2.contains("First bundle"));
assert!(result2.contains("Second bundle"));
let result3 = MergeStrategy::Composite
.merge_strings(&result2, third)
.unwrap();
assert!(result3.contains("First bundle"));
assert!(result3.contains("Second bundle"));
assert!(result3.contains("Third bundle"));
assert_eq!(result3.matches("<!-- Augent:").count(), 2);
}
#[test]
fn test_merge_replace_empty_inputs() {
let result1 = MergeStrategy::Replace
.merge_strings("existing", "")
.unwrap();
assert_eq!(result1, "");
let result2 = MergeStrategy::Replace.merge_strings("", "new").unwrap();
assert_eq!(result2, "new");
let result3 = MergeStrategy::Replace.merge_strings("", "").unwrap();
assert_eq!(result3, "");
}
#[test]
fn test_merge_shallow_json_parse_error() {
let existing = r#"{"a": 1}"#;
let new = "invalid json";
let result = MergeStrategy::Shallow.merge_strings(existing, new);
assert!(result.is_err());
}
#[test]
fn test_merge_deep_json_parse_error() {
let existing = "not valid json";
let new = r#"{"b": 2}"#;
let result = MergeStrategy::Deep.merge_strings(existing, new);
assert!(result.is_err());
}
}