deepmerge 0.1.0

Deep merge functionality with policy-driven merging and derive macro support
Documentation
//! DeepMerge implementation for toml::Value

use crate::policy::*;
use crate::{DeepMerge, MergeOutcome};
use toml::Value;
use std::mem;

impl<P: Policy> DeepMerge<P> for Value {
    fn merge_with_policy(&mut self, other: Self, policy: &P) {
        // Check condition early
        match policy.when_condition() {
            Condition::Always => {},
            Condition::NonEmpty => {
                match &other {
                    Value::Array(arr) if arr.is_empty() => return,
                    Value::Table(tbl) if tbl.is_empty() => return,
                    Value::String(s) if s.is_empty() => return,
                    _ => {},
                }
            },
            Condition::NonDefault => {
                match &other {
                    Value::Boolean(false) => return,
                    Value::Integer(0) => return,
                    Value::Float(f) if *f == 0.0 => return,
                    Value::String(s) if s.is_empty() => return,
                    Value::Array(arr) if arr.is_empty() => return,
                    Value::Table(tbl) if tbl.is_empty() => return,
                    _ => {},
                }
            },
            Condition::Some => {
                // TOML doesn't have null, so this doesn't apply
            },
            Condition::Changed => {
                if self == &other {
                    return;
                }
            },
            Condition::ChangedBy(_) => {
                // Without custom key extractors, fall back to equality
                if self == &other {
                    return;
                }
            },
        }

        // Apply merge based on types
        match (self, other) {
            // Table merging uses map policies
            (Value::Table(dst), Value::Table(src)) => {
                match policy.map_merge() {
                    MapMerge::Overlay => {
                        // Recursively merge matching keys, add new keys
                        for (k, v) in src.into_iter() {
                            match dst.get_mut(&k) {
                                Some(dst_val) => {
                                    dst_val.merge_with_policy(v, policy);
                                },
                                None => {
                                    dst.insert(k, v);
                                }
                            }
                        }
                    },
                    MapMerge::Union => {
                        // Add all keys from src, overwriting on conflict
                        dst.extend(src);
                    },
                    MapMerge::Left => {
                        // Keep only left entries
                    },
                    MapMerge::Right => {
                        // Replace with right entries
                        *dst = src;
                    },
                }
            },
            
            // Array merging uses sequence policies
            (Value::Array(dst), Value::Array(src)) => {
                match policy.sequence_merge() {
                    SequenceMerge::Append | SequenceMerge::Extend => {
                        dst.extend(src);
                    },
                    SequenceMerge::Prepend => {
                        let mut result = src;
                        result.extend(mem::take(dst));
                        *dst = result;
                    },
                    SequenceMerge::Union => {
                        // O(n^2) deduplication by equality - acceptable for config-sized arrays
                        for v in src {
                            if !dst.iter().any(|existing| existing == &v) {
                                dst.push(v);
                            }
                        }
                    },
                    SequenceMerge::Intersect => {
                        // Keep only elements that exist in both
                        let src_set = &src; // Keep reference for comparison
                        dst.retain(|x| src_set.iter().any(|y| y == x));
                    },
                }
            },
            
            // String merging uses string policies
            (Value::String(dst), Value::String(src)) => {
                match policy.string_merge() {
                    StringMerge::Replace => *dst = src,
                    StringMerge::Keep => {},
                    StringMerge::Concat => dst.push_str(&src),
                    StringMerge::ConcatWithSep(sep) => {
                        if !dst.is_empty() && !src.is_empty() {
                            dst.push_str(sep);
                        }
                        dst.push_str(&src);
                    },
                }
            },
            
            // Boolean merging uses bool policies
            (Value::Boolean(dst), Value::Boolean(src)) => {
                match policy.bool_merge() {
                    BoolMerge::Replace => *dst = src,
                    BoolMerge::Keep => {},
                    BoolMerge::TrueWins => *dst = *dst || src,
                    BoolMerge::FalseWins => *dst = *dst && src,
                }
            },
            
            // Integer merging uses number policies
            (Value::Integer(dst), Value::Integer(src)) => {
                match policy.number_merge() {
                    NumberMerge::Replace => *dst = src,
                    NumberMerge::Keep => {},
                    NumberMerge::Sum => *dst = dst.saturating_add(src),
                    NumberMerge::Max => *dst = (*dst).max(src),
                    NumberMerge::Min => *dst = (*dst).min(src),
                }
            },
            
            // Float merging uses number policies
            (Value::Float(dst), Value::Float(src)) => {
                match policy.number_merge() {
                    NumberMerge::Replace => *dst = src,
                    NumberMerge::Keep => {},
                    NumberMerge::Sum => *dst = *dst + src,
                    NumberMerge::Max => *dst = dst.max(src),
                    NumberMerge::Min => *dst = dst.min(src),
                }
            },
            
            // Mixed numeric types - convert to float
            (dst @ Value::Integer(_), Value::Float(src)) => {
                if let Value::Integer(dst_val) = dst {
                    match policy.number_merge() {
                        NumberMerge::Replace => *dst = Value::Float(src),
                        NumberMerge::Keep => {},
                        NumberMerge::Sum => *dst = Value::Float(*dst_val as f64 + src),
                        NumberMerge::Max => *dst = Value::Float((*dst_val as f64).max(src)),
                        NumberMerge::Min => *dst = Value::Float((*dst_val as f64).min(src)),
                    }
                }
            },
            (dst @ Value::Float(_), Value::Integer(src)) => {
                if let Value::Float(dst_val) = dst {
                    match policy.number_merge() {
                        NumberMerge::Replace => *dst = Value::Integer(src),
                        NumberMerge::Keep => {},
                        NumberMerge::Sum => *dst_val = *dst_val + src as f64,
                        NumberMerge::Max => *dst_val = dst_val.max(src as f64),
                        NumberMerge::Min => *dst_val = dst_val.min(src as f64),
                    }
                }
            },
            
            // Datetime as scalar - for now just replace/keep
            // Future: could add max/min for datetime comparison
            (dst @ Value::Datetime(_), Value::Datetime(src)) => {
                match policy.scalar_action() {
                    ScalarAction::Replace | ScalarAction::Merge => *dst = Value::Datetime(src),
                    ScalarAction::Keep => {},
                }
            },
            
            // Mismatched types: use scalar action
            (dst, src) => {
                match policy.scalar_action() {
                    ScalarAction::Replace => *dst = src,
                    ScalarAction::Keep => {},
                    ScalarAction::Merge => {
                        // For incompatible types, merge means replace
                        *dst = src;
                    }
                }
            }
        }
    }
    
    fn merge_with_policy_reporting(&mut self, other: Self, policy: &P) -> MergeOutcome {
        // Check if values are equal before merging
        let unchanged = self == &other;
        
        // Check condition early
        match policy.when_condition() {
            Condition::Always => {},
            Condition::NonEmpty => {
                match &other {
                    Value::Array(arr) if arr.is_empty() => return MergeOutcome::Unchanged,
                    Value::Table(tbl) if tbl.is_empty() => return MergeOutcome::Unchanged,
                    Value::String(s) if s.is_empty() => return MergeOutcome::Unchanged,
                    _ => {},
                }
            },
            Condition::NonDefault => {
                match &other {
                    Value::Boolean(false) => return MergeOutcome::Unchanged,
                    Value::Integer(0) => return MergeOutcome::Unchanged,
                    Value::Float(f) if *f == 0.0 => return MergeOutcome::Unchanged,
                    Value::String(s) if s.is_empty() => return MergeOutcome::Unchanged,
                    Value::Array(arr) if arr.is_empty() => return MergeOutcome::Unchanged,
                    Value::Table(tbl) if tbl.is_empty() => return MergeOutcome::Unchanged,
                    _ => {},
                }
            },
            Condition::Some => {
                // TOML doesn't have null
            },
            Condition::Changed => {
                if unchanged {
                    return MergeOutcome::Unchanged;
                }
            },
            Condition::ChangedBy(_) => {
                if unchanged {
                    return MergeOutcome::Unchanged;
                }
            },
        }
        
        // Store original for comparison
        let original = self.clone();
        
        // Perform merge
        self.merge_with_policy(other, policy);
        
        // Check if anything changed
        if &original == self {
            MergeOutcome::Unchanged
        } else {
            MergeOutcome::Changed
        }
    }
}