merge/
lib.rs

1// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
2// SPDX-License-Identifier: Apache-2.0 or MIT
3
4//! Provides [`Merge`][], a trait for objects that can be merged.
5//!
6//! # Usage
7//!
8//! ```
9//! trait Merge {
10//!     fn merge(&mut self, other: Self);
11//! }
12//! ```
13//!
14//! The [`Merge`][] trait can be used to merge two objects of the same type into one.  The intended
15//! use case is merging configuration from different sources, for example environment variables,
16//! multiple configuration files and command-line arguments, see the [`args.rs`][] example.
17//!
18//! This crate does not provide any `Merge` implementations, but `Merge` can be derived for
19//! structs.  When deriving the `Merge` trait for a struct, you can provide custom merge strategies
20//! for the fields that don’t implement `Merge`.  A merge strategy is a function with the signature
21//! `fn merge<T>(left: &mut T, right: T)` that merges `right` into `left`.  The submodules of this
22//! crate provide strategies for the most common types, but you can also define your own
23//! strategies.
24//!
25//! ## Features
26//!
27//! This crate has the following features:
28//!
29//! - `derive` (default):  Enables the derive macro for the `Merge` trait using the `merge_derive`
30//!   crate.
31//! - `num` (default): Enables the merge strategies in the `num` module that require the
32//!   `num_traits` crate.
33//! - `std` (default): Enables the merge strategies in the `hashmap` and `vec` modules that require
34//!   the standard library.  If this feature is not set, `merge` is a `no_std` library.
35//!
36//! # Example
37//!
38//! ```
39//! use merge::Merge;
40//!
41//! #[derive(Merge)]
42//! struct User {
43//!     // Fields with the skip attribute are skipped by Merge
44//!     #[merge(skip)]
45//!     pub name: &'static str,
46//!
47//!     // The strategy attribute is used to customize the merge behavior
48//!     #[merge(strategy = merge::option::overwrite_none)]
49//!     pub location: Option<&'static str>,
50//!
51//!     #[merge(strategy = merge::vec::append)]
52//!     pub groups: Vec<&'static str>,
53//! }
54//!
55//! let defaults = User {
56//!     name: "",
57//!     location: Some("Internet"),
58//!     groups: vec!["rust"],
59//! };
60//! let mut ferris = User {
61//!     name: "Ferris",
62//!     location: None,
63//!     groups: vec!["mascot"],
64//! };
65//! ferris.merge(defaults);
66//!
67//! assert_eq!("Ferris", ferris.name);
68//! assert_eq!(Some("Internet"), ferris.location);
69//! assert_eq!(vec!["mascot", "rust"], ferris.groups);
70//! ```
71//!
72//! [`Merge`]: trait.Merge.html
73//! [`args.rs`]: https://git.sr.ht/~ireas/merge-rs/tree/master/examples/args.rs
74
75#![cfg_attr(not(feature = "std"), no_std)]
76
77#[cfg(feature = "derive")]
78pub use merge_derive::*;
79
80/// A trait for objects that can be merged.
81///
82/// # Deriving
83///
84/// `Merge` can be derived for structs if the `derive` feature is enabled.  The generated
85/// implementation calls the `merge` method for all fields, or the merge strategy function if set.
86/// You can use these field attributes to configure the generated implementation:
87/// - `skip`: Skip this field in the `merge` method.
88/// - `strategy = f`: Call `f(self.field, other.field)` instead of calling the `merge` function for
89///   this field.
90///
91/// You can also set a default strategy for all fields by setting the `strategy` attribute for the
92/// struct.
93///
94/// # Examples
95///
96/// Deriving `Merge` for a struct:
97///
98/// ```
99/// use merge::Merge;
100///
101/// #[derive(Debug, PartialEq, Merge)]
102/// struct S {
103///     #[merge(strategy = merge::option::overwrite_none)]
104///     option: Option<usize>,
105///
106///     #[merge(skip)]
107///     s: String,
108///
109///     #[merge(strategy = merge::bool::overwrite_false)]
110///     flag: bool,
111/// }
112///
113/// let mut val = S {
114///     option: None,
115///     s: "some ignored value".to_owned(),
116///     flag: false,
117/// };
118/// val.merge(S {
119///     option: Some(42),
120///     s: "some other ignored value".to_owned(),
121///     flag: true,
122/// });
123/// assert_eq!(S {
124///     option: Some(42),
125///     s: "some ignored value".to_owned(),
126///     flag: true,
127/// }, val);
128/// ```
129///
130/// Setting a default merge strategy:
131///
132/// ```
133/// use merge::Merge;
134///
135/// #[derive(Debug, PartialEq, Merge)]
136/// #[merge(strategy = merge::option::overwrite_none)]
137/// struct S {
138///     option1: Option<usize>,
139///     option2: Option<usize>,
140///     option3: Option<usize>,
141/// }
142///
143/// let mut val = S {
144///     option1: None,
145///     option2: Some(1),
146///     option3: None,
147/// };
148/// val.merge(S {
149///     option1: Some(2),
150///     option2: Some(2),
151///     option3: None,
152/// });
153/// assert_eq!(S {
154///     option1: Some(2),
155///     option2: Some(1),
156///     option3: None,
157/// }, val);
158/// ```
159pub trait Merge {
160    /// Merge another object into this object.
161    fn merge(&mut self, other: Self);
162}
163
164/// Merge strategies for `Option`
165pub mod option {
166    /// Overwrite `left` with `right` only if `left` is `None`.
167    pub fn overwrite_none<T>(left: &mut Option<T>, right: Option<T>) {
168        if left.is_none() {
169            *left = right;
170        }
171    }
172
173    /// If both `left` and `right` are `Some`, recursively merge the two.
174    /// Otherwise, fall back to `overwrite_none`.
175    pub fn recurse<T: crate::Merge>(left: &mut Option<T>, right: Option<T>) {
176        if let Some(new) = right {
177            if let Some(original) = left {
178                original.merge(new);
179            } else {
180                *left = Some(new);
181            }
182        }
183    }
184}
185
186/// Merge strategies for boolean types.
187pub mod bool {
188    /// Overwrite left with right if the value of left is false.
189    pub fn overwrite_false(left: &mut bool, right: bool) {
190        if !*left {
191            *left = right;
192        }
193    }
194
195    /// Overwrite left with right if the value of left is true.
196    pub fn overwrite_true(left: &mut bool, right: bool) {
197        if *left {
198            *left = right;
199        }
200    }
201}
202
203/// Merge strategies for numeric types.
204///
205/// These strategies are only available if the `num` feature is enabled.
206#[cfg(feature = "num")]
207pub mod num {
208    /// Set left to the saturated some of left and right.
209    pub fn saturating_add<T: num_traits::SaturatingAdd>(left: &mut T, right: T) {
210        *left = left.saturating_add(&right);
211    }
212
213    /// Overwrite left with right if the value of left is zero.
214    pub fn overwrite_zero<T: num_traits::Zero>(left: &mut T, right: T) {
215        if left.is_zero() {
216            *left = right;
217        }
218    }
219}
220
221/// Merge strategies for types that form a total order.
222pub mod ord {
223    use core::cmp;
224
225    /// Set left to the maximum of left and right.
226    pub fn max<T: cmp::Ord>(left: &mut T, right: T) {
227        if cmp::Ord::cmp(left, &right) == cmp::Ordering::Less {
228            *left = right;
229        }
230    }
231
232    /// Set left to the minimum of left and right.
233    pub fn min<T: cmp::Ord>(left: &mut T, right: T) {
234        if cmp::Ord::cmp(left, &right) == cmp::Ordering::Greater {
235            *left = right;
236        }
237    }
238}
239
240/// Merge strategies for vectors.
241///
242/// These strategies are only available if the `std` feature is enabled.
243#[cfg(feature = "std")]
244pub mod vec {
245    /// Overwrite left with right if left is empty.
246    pub fn overwrite_empty<T>(left: &mut Vec<T>, mut right: Vec<T>) {
247        if left.is_empty() {
248            left.append(&mut right);
249        }
250    }
251
252    /// Append the contents of right to left.
253    pub fn append<T>(left: &mut Vec<T>, mut right: Vec<T>) {
254        left.append(&mut right);
255    }
256
257    /// Prepend the contents of right to left.
258    pub fn prepend<T>(left: &mut Vec<T>, mut right: Vec<T>) {
259        right.append(left);
260        *left = right;
261    }
262}
263
264/// Merge strategies for hash maps.
265///
266/// These strategies are only available if the `std` feature is enabled.
267#[cfg(feature = "std")]
268pub mod hashmap {
269    use std::collections::HashMap;
270    use std::hash::Hash;
271
272    /// On conflict, overwrite elements of `left` with `right`.
273    ///
274    /// In other words, this gives precedence to `right`.
275    pub fn overwrite<K: Eq + Hash, V>(left: &mut HashMap<K, V>, right: HashMap<K, V>) {
276        left.extend(right)
277    }
278
279    /// On conflict, ignore elements from `right`.
280    ///
281    /// In other words, this gives precedence to `left`.
282    pub fn ignore<K: Eq + Hash, V>(left: &mut HashMap<K, V>, right: HashMap<K, V>) {
283        for (k, v) in right {
284            left.entry(k).or_insert(v);
285        }
286    }
287
288    /// On conflict, recursively merge the elements.
289    pub fn recurse<K: Eq + Hash, V: crate::Merge>(left: &mut HashMap<K, V>, right: HashMap<K, V>) {
290        use std::collections::hash_map::Entry;
291
292        for (k, v) in right {
293            match left.entry(k) {
294                Entry::Occupied(mut existing) => existing.get_mut().merge(v),
295                Entry::Vacant(empty) => {
296                    empty.insert(v);
297                }
298            }
299        }
300    }
301}