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//! `Merge` is implemented for `Option` and can be derived for structs.  When deriving the `Merge`
19//! trait for a struct, you can provide custom merge strategies for the fields that don’t implement
20//! `Merge`.  A merge strategy is a function with the signature `fn merge<T>(left: &mut T, right:
21//! T)` that merges `right` into `left`.  The submodules of this crate provide strategies for the
22//! most common types, but you can also define your own strategies.
23//!
24//! ## Features
25//!
26//! This crate has the following features:
27//!
28//! - `derive` (default):  Enables the derive macro for the `Merge` trait using the `merge_derive`
29//!   crate.
30//! - `num` (default): Enables the merge strategies in the `num` module that require the
31//!   `num_traits` crate.
32//! - `std` (default): Enables the merge strategies in the `vec` module that require the standard
33//!   library.  If this feature is not set, `merge` is a `no_std` library.
34//!
35//! # Example
36//!
37//! ```
38//! use merge::Merge;
39//!
40//! #[derive(Merge)]
41//! struct User {
42//!     // Fields with the skip attribute are skipped by Merge
43//!     #[merge(skip)]
44//!     pub name: &'static str,
45//!
46//!     // The Merge implementation for Option replaces its value if it is None
47//!     pub location: Option<&'static str>,
48//!
49//!     // The strategy attribute is used to customize the merge behavior
50//!     #[merge(strategy = merge::vec::append)]
51//!     pub groups: Vec<&'static str>,
52//! }
53//!
54//! let defaults = User {
55//!     name: "",
56//!     location: Some("Internet"),
57//!     groups: vec!["rust"],
58//! };
59//! let mut ferris = User {
60//!     name: "Ferris",
61//!     location: None,
62//!     groups: vec!["mascot"],
63//! };
64//! ferris.merge(defaults);
65//!
66//! assert_eq!("Ferris", ferris.name);
67//! assert_eq!(Some("Internet"), ferris.location);
68//! assert_eq!(vec!["mascot", "rust"], ferris.groups);
69//! ```
70//!
71//! [`Merge`]: trait.Merge.html
72//! [`args.rs`]: https://git.sr.ht/~ireas/merge-rs/tree/master/examples/args.rs
73
74#![cfg_attr(not(feature = "std"), no_std)]
75
76#[cfg(feature = "derive")]
77pub use merge_derive::*;
78
79/// A trait for objects that can be merged.
80///
81/// # Deriving
82///
83/// `Merge` can be derived for structs if the `derive` feature is enabled.  The generated
84/// implementation calls the `merge` method for all fields, or the merge strategy function if set.
85/// You can use these field attributes to configure the generated implementation:
86/// - `skip`: Skip this field in the `merge` method.
87/// - `strategy = f`: Call `f(self.field, other.field)` instead of calling the `merge` function for
88///    this field.
89///
90/// # Examples
91///
92/// Using the `Merge` implementation for `Option`:
93///
94/// ```
95/// use merge::Merge as _;
96///
97/// let mut val = None;
98/// val.merge(Some(42));
99/// assert_eq!(Some(42), val);
100/// ```
101///
102/// Deriving `Merge` for a struct:
103///
104/// ```
105/// use merge::Merge;
106///
107/// #[derive(Debug, PartialEq, Merge)]
108/// struct S {
109///     option: Option<usize>,
110///
111///     #[merge(skip)]
112///     s: String,
113///
114///     #[merge(strategy = merge::bool::overwrite_false)]
115///     flag: bool,
116/// }
117///
118/// let mut val = S {
119///     option: None,
120///     s: "some ignored value".to_owned(),
121///     flag: false,
122/// };
123/// val.merge(S {
124///     option: Some(42),
125///     s: "some other ignored value".to_owned(),
126///     flag: true,
127/// });
128/// assert_eq!(S {
129///     option: Some(42),
130///     s: "some ignored value".to_owned(),
131///     flag: true,
132/// }, val);
133/// ```
134pub trait Merge {
135    /// Merge another object into this object.
136    fn merge(&mut self, other: Self);
137}
138
139impl<T> Merge for Option<T> {
140    fn merge(&mut self, mut other: Self) {
141        if !self.is_some() {
142            *self = other.take();
143        }
144    }
145}
146
147/// Merge strategies for boolean types.
148pub mod bool {
149    /// Overwrite left with right if the value of left is false.
150    pub fn overwrite_false(left: &mut bool, right: bool) {
151        if !*left {
152            *left = right;
153        }
154    }
155
156    /// Overwrite left with right if the value of left is true.
157    pub fn overwrite_true(left: &mut bool, right: bool) {
158        if *left {
159            *left = right;
160        }
161    }
162}
163
164/// Merge strategies for numeric types.
165///
166/// These strategies are only available if the `num` feature is enabled.
167#[cfg(feature = "num")]
168pub mod num {
169    /// Set left to the saturated some of left and right.
170    pub fn saturating_add<T: num_traits::SaturatingAdd>(left: &mut T, right: T) {
171        *left = left.saturating_add(&right);
172    }
173
174    /// Overwrite left with right if the value of left is zero.
175    pub fn overwrite_zero<T: num_traits::Zero>(left: &mut T, right: T) {
176        if left.is_zero() {
177            *left = right;
178        }
179    }
180}
181
182/// Merge strategies for types that form a total order.
183pub mod ord {
184    use core::cmp;
185
186    /// Set left to the maximum of left and right.
187    pub fn max<T: cmp::Ord>(left: &mut T, right: T) {
188        if cmp::Ord::cmp(left, &right) == cmp::Ordering::Less {
189            *left = right;
190        }
191    }
192
193    /// Set left to the minimum of left and right.
194    pub fn min<T: cmp::Ord>(left: &mut T, right: T) {
195        if cmp::Ord::cmp(left, &right) == cmp::Ordering::Greater {
196            *left = right;
197        }
198    }
199}
200
201/// Merge strategies for vectors.
202///
203/// These strategies are only available if the `std` feature is enabled.
204#[cfg(feature = "std")]
205pub mod vec {
206    /// Overwrite left with right if left is empty.
207    pub fn overwrite_empty<T>(left: &mut Vec<T>, mut right: Vec<T>) {
208        if left.is_empty() {
209            left.append(&mut right);
210        }
211    }
212
213    /// Append the contents of right to left.
214    pub fn append<T>(left: &mut Vec<T>, mut right: Vec<T>) {
215        left.append(&mut right);
216    }
217
218    /// Prepend the contents of right to left.
219    pub fn prepend<T>(left: &mut Vec<T>, mut right: Vec<T>) {
220        right.append(left);
221        *left = right;
222    }
223}