Crate deepmerge

Crate deepmerge 

Source
Expand description

§DeepMerge

A flexible deep merge library for Rust with policy-driven merging and derive macros featuring typed attributes.

§Features

  • Policy-driven merging: Configure how different types should be merged
  • Derive macro support: Automatically implement DeepMerge for your structs
  • Typed attributes: Use identifiers instead of string literals for better compile-time checking
  • Flexible attribute syntax: Mix string literals, identifiers, and path expressions
  • Precedence rules: Field-level > struct-level > caller-provided policies
  • Multiple merge strategies: Append, prepend, union, concatenation, replacement, and more
  • No-std compatible: Works without the standard library (with alloc)

§Quick Start

Add this to your Cargo.toml:

[dependencies]
deepmerge = { version = "0.1", features = ["derive"] }

§Usage Examples

§Basic Usage with Typed Attributes

use deepmerge::prelude::*;

// Use clean identifier syntax instead of string literals
#[derive(DeepMerge, Debug)]
#[merge(policy(string = concat, sequence = append, bool = true_wins))]
struct Config {
    pub title: String,
    pub tags: Vec<String>,
    pub enabled: bool,
    
    // Field-level override
    #[merge(string = keep)]
    pub version: String,
}

let mut config = Config {
    title: "My App".to_string(),
    tags: vec!["web".to_string()],
    enabled: false,
    version: "1.0".to_string(),
};

let update = Config {
    title: " v2".to_string(),
    tags: vec!["api".to_string()],
    enabled: true,
    version: "2.0".to_string(),
};

config.merge_with_policy(update, &DefaultPolicy);

// Results:
// title: "My App v2" (concatenated)
// tags: ["web", "api"] (appended)  
// enabled: true (true wins)
// version: "1.0" (kept original due to field-level override)

§Supported Attribute Syntax

use deepmerge::prelude::*;

#[derive(DeepMerge)]
#[merge(policy(
    // String literals (traditional)
    string = "concat",
    
    // Identifiers (new, cleaner syntax) 
    sequence = append,
    bool = true_wins,
    number = sum,
    
    // Mixed syntax is supported
    map = "overlay"
))]
struct FlexibleConfig { /* ... */ }

§Available Merge Policies

String Policies: concat, keep, replace
Sequence Policies: append, prepend, union, extend, intersect
Boolean Policies: true_wins, false_wins, replace, keep
Number Policies: sum, max, min, replace, keep
Map Policies: overlay, union, left, right
Option Policies: take, preserve, or_left

§Policy Precedence

Policies are applied in this order of precedence:

  1. Field-level attributes (highest priority)
  2. Struct-level attributes
  3. Caller-provided policies (lowest priority)

§Advanced Features

§Future Path Expression Support

The infrastructure is ready for advanced path expressions:

// Coming soon:
// #[merge(policy(string = StringMerge::ConcatWithSep(", ")))]

§Condition Policies with Key Extractors

// Coming soon:
// #[merge(policy(condition = changed, changed_by = "key_extractor_fn"))]

§Feature Flags

  • derive (default): Enable the derive macro
  • std (default): Enable standard library support
  • alloc (default): Enable allocation support
  • indexmap: Add support for IndexMap types

§License

Licensed under either of:

  • Apache License, Version 2.0
  • MIT License

at your option.

§Contributing

Contributions are welcome! Please feel free to submit a Pull Request. A flexible deep merge library for Rust with policy-driven merging and derive macros featuring typed attributes.

This crate provides comprehensive deep merge functionality with compile-time configuration through derive macros, policy-driven behavior, and support for complex data structures.

§Features

  • Policy-driven merging: Configure how different types should be merged
  • Derive macro support: Automatically implement DeepMerge for your structs
  • Typed attributes: Use identifiers instead of string literals for better compile-time checking
  • Flexible attribute syntax: Mix string literals, identifiers, and path expressions
  • Precedence rules: Field-level > struct-level > caller-provided policies
  • Multiple merge strategies: Append, prepend, union, concatenation, replacement, and more
  • No-std compatible: Works without the standard library (with alloc)

§Quick Start with Prelude

For convenience, import everything you need with the prelude:

use deepmerge::prelude::*;

#[derive(DeepMerge)]
struct AppConfig {
    name: String,
    port: u16,
}

let mut config = AppConfig { 
    name: "myapp".to_string(), 
    port: 8080 
};

let update = AppConfig { 
    name: "newname".to_string(), 
    port: 9090 
};

config.merge(update);
assert_eq!(config.name, "newname");
assert_eq!(config.port, 9090);

§Basic Usage with Explicit Policies

use deepmerge::prelude::*;

// Simple derive without policy attributes
#[derive(DeepMerge, Debug)]
struct Config {
    pub title: String,
    pub tags: Vec<String>,
    pub enabled: bool,
    pub version: String,
}

let mut config = Config {
    title: "My App".to_string(),
    tags: vec!["web".to_string()],
    enabled: false,
    version: "1.0".to_string(),
};

let update = Config {
    title: " v2".to_string(),
    tags: vec!["api".to_string()],
    enabled: true,
    version: "2.0".to_string(),
};

// Use explicit policy for complex merge behavior
let policy = ComposedPolicy::new(DefaultPolicy)
    .with_string_merge(StringMerge::Concat)
    .with_sequence_merge(SequenceMerge::Append)
    .with_bool_merge(BoolMerge::TrueWins);
     
config.merge_with_policy(update, &policy);

// Results:
assert_eq!(config.title, "My App v2"); // concatenated
assert_eq!(config.tags, vec!["web", "api"]); // appended  
assert_eq!(config.enabled, true); // true wins
assert_eq!(config.version, "2.0"); // replaced (no field-level override available yet)

§Policy Configuration

Currently, policy configuration is done through explicit ComposedPolicy usage. Derive macro policy attributes are temporarily disabled due to trait system complexity.

use deepmerge::prelude::*;

#[derive(DeepMerge)]
struct FlexibleConfig {
    name: String,
    items: Vec<i32>,
    enabled: bool,
    count: i32,
}

// Configure policies explicitly
let policy = ComposedPolicy::new(DefaultPolicy)
    .with_string_merge(StringMerge::Concat)
    .with_sequence_merge(SequenceMerge::Append) 
    .with_bool_merge(BoolMerge::TrueWins)
    .with_number_merge(NumberMerge::Sum)
    .with_map_merge(MapMerge::Overlay);

let mut config = FlexibleConfig { /* ... */ };
let update = FlexibleConfig { /* ... */ };
config.merge_with_policy(update, &policy);

§Available Merge Policies

  • String Policies: concat, keep, replace
  • Sequence Policies: append, prepend, union, extend, intersect
  • Boolean Policies: true_wins, false_wins, replace, keep
  • Number Policies: sum, max, min, replace, keep
  • Map Policies: overlay, union, left, right
  • Option Policies: take, preserve, or_left

§Using the Prelude

The prelude module provides all commonly used items in a single import:

// Import everything you need with one line
use deepmerge::prelude::*;

// Now you have access to:
// - DeepMerge trait and derive macro
// - All policy types (DefaultPolicy, ComposedPolicy)
// - All merge strategy enums (StringMerge, SequenceMerge, etc.)
// - All convenience functions (deep_merge, merged, etc.)

For version stability, you can also import from a specific version:

use deepmerge::prelude::v1::*;

§Policy Usage

Currently, policies are configured explicitly through ComposedPolicy. This provides fine-grained control over merge behavior.

use deepmerge::prelude::*;

#[derive(DeepMerge, Debug)]
struct App {
    name: String,
    count: i32,
}

let mut a = App { name: "svc".into(), count: 2 };
let b = App { name: "new".into(), count: 3 };

// Configure specific merge behavior
let policy = ComposedPolicy::new(DefaultPolicy)
    .with_string_merge(StringMerge::Replace)
    .with_number_merge(NumberMerge::Sum);

a.merge_with_policy(b, &policy);
assert_eq!(a.name, "new"); // replaced
assert_eq!(a.count, 5);    // summed (2 + 3)

§Per-policy examples (concise)

  • String policies:
    • Replace (default)
    • Keep
    • Concat / ConcatWithSep
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct S { s: String }
let mut a = S { s: "a".into() };
let policy = ComposedPolicy::new(DefaultPolicy).with_string_merge(StringMerge::Concat);
a.merge_with_policy(S { s: "b".into() }, &policy);
assert_eq!(a.s, "ab");
  • Number policies: Replace (default), Keep, Sum, Max, Min
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct N { n: i32 }
let mut a = N { n: 2 };
let policy = ComposedPolicy::new(DefaultPolicy).with_number_merge(NumberMerge::Max);
a.merge_with_policy(N { n: 5 }, &policy);
assert_eq!(a.n, 5);
  • Bool policies: Replace (default), Keep, TrueWins, FalseWins
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct B { b: bool }
let mut a = B { b: false };
let policy = ComposedPolicy::new(DefaultPolicy).with_bool_merge(BoolMerge::TrueWins);
a.merge_with_policy(B { b: true }, &policy);
assert!(a.b);
  • Sequence policies: Append (default), Prepend, Extend, Union, Intersect
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
struct L { v: Vec<i32> }
let mut a = L { v: vec![1,2] };
let policy = ComposedPolicy::new(DefaultPolicy).with_sequence_merge(SequenceMerge::Append);
a.merge_with_policy(L { v: vec![2,3] }, &policy);
assert_eq!(a.v, vec![1,2,2,3]);
  • Map policies: Overlay (default), Union, Left, Right
use std::collections::HashMap;
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
#[merge(policy(map = overlay))]
struct M { m: HashMap<&'static str, i32> }
let mut a = M { m: [("a",1)].into_iter().collect() };
a.merge(M { m: [("a",2),("b",3)].into_iter().collect() });
assert_eq!(a.m.get("a"), Some(&2));
assert_eq!(a.m.get("b"), Some(&3));
  • Option policies: Take (default), Preserve, OrLeft
use deepmerge::prelude::*;
#[derive(DeepMerge, Debug)]
#[merge(policy(option = preserve))]
struct O { o: Option<i32> }
let mut a = O { o: Some(1) };
a.merge(O { o: Some(2) });
assert_eq!(a.o, Some(1));

Re-exports§

pub use policy::Policy;
pub use policy::DefaultPolicy;
pub use policy::DeepMerge;
pub use policy::DeepMergeDefault;
pub use policy::DeepMergeFrom;
pub use policy::DeepMergeFromDefault;
pub use policy::StrictReplacePolicy;
pub use policy::PreservePolicy;
pub use policy::DedupeCollectionsPolicy;
pub use policy::ComposedPolicy;
pub use policy::ScalarAction;
pub use policy::SequenceMerge;
pub use policy::OptionMerge;
pub use policy::NumberMerge;
pub use policy::MapMerge;
pub use policy::BoolMerge;
pub use policy::StringMerge;
pub use policy::Condition;
pub use policy::MergeOutcome;
pub use handlers::vec_union_dedup;
pub use handlers::vec_intersect_dedup;
pub use handlers::vec_append_with_dedupe;
pub use handlers::vec_prepend_with_dedupe;
pub use handlers::vec_dedupe_in_place;
pub use handlers::vec_dedupe_in_place_by_key;
pub use handlers::vec_append_dedup_by_key;
pub use handlers::vec_prepend_dedup_by_key;
pub use handlers::vec_union_dedup_ord;
pub use handlers::vec_intersect_dedup_ord;
pub use handlers::option_merge_if_changed;
pub use handlers::option_merge_with_key;

Modules§

forwarding
Forwarding implementations for smart pointers and borrowed types
handlers
Kind-specific merge handlers
policy
Policy system for configuring merge behavior
prelude
Prelude module for convenient imports

Functions§

deep_merge
Deep merge two values using the default policy
deep_merge_from
Deep merge from another type using the default policy
deep_merge_from_reporting
Deep merge from another type with change reporting using the default policy
deep_merge_from_with_policy
Deep merge from another type using a specific policy
deep_merge_from_with_policy_reporting
Deep merge from another type with change reporting using a specific policy
deep_merge_ref
Deep merge by reference using the default policy
deep_merge_ref_with_policy
Deep merge by reference using a specific policy
deep_merge_reporting
Deep merge with change reporting using the default policy
deep_merge_with_policy
Deep merge two values using a specific policy
deep_merge_with_policy_reporting
Deep merge with change reporting using a specific policy
merged
Merge two values and return a new merged value using the default policy This provides parity with deep_merge but returns a new value instead of mutating
merged_with_policy
Merge two values and return a new merged value using a specific policy

Derive Macros§

DeepMerge
Derive macro for the DeepMerge trait