deepmerge 0.1.0

Deep merge functionality with policy-driven merging and derive macro support
Documentation
use deepmerge::prelude::*;
use std::collections::HashMap;

#[test]
fn test_number_replace() {
    let mut dst = 42;
    let src = 100;
    dst.merge(src);
    assert_eq!(dst, 100);
}

#[test]
fn test_number_with_policy() {
    // Test Max
    let mut policy = ComposedPolicy::new(DefaultPolicy);
    policy.number_merge = Some(NumberMerge::Max);
    
    let mut dst = 42;
    dst.merge_with_policy(100, &policy);
    assert_eq!(dst, 100);
    
    let mut dst = 100;
    dst.merge_with_policy(42, &policy);
    assert_eq!(dst, 100);
    
    // Test Min
    policy.number_merge = Some(NumberMerge::Min);
    let mut dst = 42;
    dst.merge_with_policy(100, &policy);
    assert_eq!(dst, 42);
    
    // Test Sum
    policy.number_merge = Some(NumberMerge::Sum);
    let mut dst = 42;
    dst.merge_with_policy(100, &policy);
    assert_eq!(dst, 142);
}

#[test]
fn test_bool_merge() {
    // Default replaces
    let mut dst = false;
    dst.merge(true);
    assert!(dst);
    
    // TrueWins
    let mut policy = ComposedPolicy::new(DefaultPolicy);
    policy.bool_merge = Some(BoolMerge::TrueWins);
    
    let mut dst = false;
    dst.merge_with_policy(true, &policy);
    assert!(dst);
    
    let mut dst = true;
    dst.merge_with_policy(false, &policy);
    assert!(dst);
    
    // FalseWins
    policy.bool_merge = Some(BoolMerge::FalseWins);
    
    let mut dst = true;
    dst.merge_with_policy(false, &policy);
    assert!(!dst);
    
    let mut dst = false;
    dst.merge_with_policy(true, &policy);
    assert!(!dst);
}

#[test]
fn test_string_merge() {
    // Default replaces
    let mut dst = "original".to_string();
    dst.merge("replaced".to_string());
    assert_eq!(dst, "replaced");
    
    // With NonEmpty condition
    let mut policy = ComposedPolicy::new(DefaultPolicy);
    policy.when_condition = Some(Condition::NonEmpty);
    
    let mut dst = "original".to_string();
    dst.merge_with_policy(String::new(), &policy);
    assert_eq!(dst, "original"); // Empty string doesn't replace
    
    let mut dst = "original".to_string();
    dst.merge_with_policy("replaced".to_string(), &policy);
    assert_eq!(dst, "replaced");
}

#[test]
fn test_option_merge() {
    // Default takes Some
    let mut dst = Some("original".to_string());
    dst.merge(Some("replaced".to_string()));
    assert_eq!(dst, Some("replaced".to_string()));
    
    let mut dst = Some("original".to_string());
    dst.merge(None);
    assert_eq!(dst, Some("original".to_string()));
    
    let mut dst: Option<String> = None;
    dst.merge(Some("new".to_string()));
    assert_eq!(dst, Some("new".to_string()));
    
    // Preserve policy
    let policy = PreservePolicy;
    let mut dst = Some("original".to_string());
    dst.merge_with_policy(Some("replaced".to_string()), &policy);
    assert_eq!(dst, Some("original".to_string()));
    
    // OrLeft policy
    let mut policy = ComposedPolicy::new(DefaultPolicy);
    policy.option_merge = Some(OptionMerge::OrLeft);
    
    let mut dst = None;
    dst.merge_with_policy(Some("fallback".to_string()), &policy);
    assert_eq!(dst, Some("fallback".to_string()));
    
    let mut dst = Some("original".to_string());
    dst.merge_with_policy(Some("ignored".to_string()), &policy);
    assert_eq!(dst, Some("original".to_string()));
}

#[test]
fn test_vec_merge() {
    // Default appends
    let mut dst = vec![1, 2, 3];
    dst.merge(vec![4, 5]);
    assert_eq!(dst, vec![1, 2, 3, 4, 5]);
    
    // Prepend
    let mut policy = ComposedPolicy::new(DefaultPolicy);
    policy.sequence_merge = Some(deepmerge::SequenceMerge::Prepend);
    
    let mut dst = vec![3, 4];
    dst.merge_with_policy(vec![1, 2], &policy);
    assert_eq!(dst, vec![1, 2, 3, 4]);
}

#[test]
fn test_hashmap_merge() {
    // Default overlays with deep merge
    let mut dst = HashMap::new();
    dst.insert("a", vec![1, 2]);
    dst.insert("b", vec![3, 4]);
    
    let mut src = HashMap::new();
    src.insert("b", vec![5, 6]); // Will append to existing
    src.insert("c", vec![7, 8]); // New key
    
    dst.merge(src);
    
    assert_eq!(dst.get("a"), Some(&vec![1, 2])); // Preserved
    assert_eq!(dst.get("b"), Some(&vec![3, 4, 5, 6])); // Deep merged
    assert_eq!(dst.get("c"), Some(&vec![7, 8])); // Added
}

#[test]
fn test_conditions() {
    // NonDefault condition
    let mut policy = ComposedPolicy::new(DefaultPolicy);
    policy.when_condition = Some(Condition::NonDefault);
    
    let mut dst = 42;
    dst.merge_with_policy(0, &policy); // 0 is default for numbers
    assert_eq!(dst, 42); // Not changed
    
    dst.merge_with_policy(100, &policy);
    assert_eq!(dst, 100); // Changed
    
    // Changed condition
    policy.when_condition = Some(Condition::Changed);
    
    let mut dst = 42;
    dst.merge_with_policy(42, &policy);
    assert_eq!(dst, 42); // Not changed (same value)
    
    dst.merge_with_policy(100, &policy);
    assert_eq!(dst, 100); // Changed (different value)
}

#[test]
fn test_strict_replace_policy() {
    let policy = StrictReplacePolicy;
    
    // Replaces everything, no deep merge
    let mut dst = HashMap::new();
    dst.insert("a", 1);
    dst.insert("b", 2);
    
    let mut src = HashMap::new();
    src.insert("c", 3);
    
    dst.merge_with_policy(src, &policy);
    
    assert_eq!(dst.len(), 1);
    assert_eq!(dst.get("c"), Some(&3));
}

#[test]
fn test_preserve_policy() {
    let policy = PreservePolicy;
    
    // Keeps left values - integers
    let mut dst = 42i32;
    dst.merge_with_policy(100, &policy);
    assert_eq!(dst, 42);
    
    // Keeps left values - floats
    let mut dst = 3.15f64;
    dst.merge_with_policy(2.71, &policy);
    assert_eq!(dst, 3.15);
    
    // Keeps left values - booleans
    let mut dst = true;
    dst.merge_with_policy(false, &policy);
    assert!(dst);
    
    let mut dst = false;
    dst.merge_with_policy(true, &policy);
    assert!(!dst);
    
    // Keeps left values - strings
    let mut dst = "original".to_string();
    dst.merge_with_policy("ignored".to_string(), &policy);
    assert_eq!(dst, "original");
    
    // Test all number types
    let mut dst_i8: i8 = 42;
    dst_i8.merge_with_policy(100, &policy);
    assert_eq!(dst_i8, 42);
    
    let mut dst_u64: u64 = 42;
    dst_u64.merge_with_policy(100, &policy);
    assert_eq!(dst_u64, 42);
    
    let mut dst_f32: f32 = 42.0;
    dst_f32.merge_with_policy(100.0, &policy);
    assert_eq!(dst_f32, 42.0);
}

#[test]
fn test_number_keep_variant() {
    // Test the Keep variant directly
    let policy = ComposedPolicy::new(DefaultPolicy)
        .with_number_merge(NumberMerge::Keep);
    
    let mut dst = 42;
    dst.merge_with_policy(100, &policy);
    assert_eq!(dst, 42); // Should keep the left value
    
    let mut dst = -5;
    dst.merge_with_policy(20, &policy);
    assert_eq!(dst, -5); // Should keep the left value
}