selective_assertions/impls/
assert_eq_excluding.rs

1/// Asserts that two values are equal, excluding the specified fields.
2///
3/// The fields listed in the macro are set to the values copied from the expected
4/// value before performing the equality check.
5///
6/// This allows you to assert that all other fields are equal while excluding the
7/// specified ones.
8///
9/// # Syntax
10///
11/// `assert_eq_excluding!(actual_value, expected_value, field1, field2, ...);`
12///
13/// - `actual_value`: The actual value to compare.
14/// - `expected_value`: The expected value to compare against.
15/// - `field1, field2, ...`: The names of the fields to exclude during the comparison.
16///
17/// # Example
18///
19/// If you want to compare two `User` instances while excluding the `age` as follows:
20///
21/// `assert_eq_excluding!(user1, user2, age);`
22///
23/// ```rust
24/// use derive_getters::Getters;
25/// use getset::Setters;
26/// use selective_assertions::*;
27///
28/// #[derive(Debug, PartialEq, Clone, Getters, Setters)]
29/// #[set = "pub"]
30/// pub struct User {
31///     id: u32,
32///     name: String,
33///     age: u8,
34/// }
35///
36/// let user1 = User { id: 1, name: "Alice".to_string(), age: 7 };
37/// let user2 = User { id: 1, name: "Alice".to_string(), age: 8 };
38///
39/// // Compare user1 and user2, excluding the `age` field
40/// assert_eq_excluding!(user1, user2, age); // This will pass
41/// ```
42///
43/// # Panics
44///
45/// This macro will panic if the actual and expected values are not equal when excluding
46/// the specified fields.
47///
48/// # Note
49///
50/// The macro requires getter and setter methods.
51///
52/// It is recommended to use `derive_getters::Getters` and `getset::Setters` crates.
53#[macro_export]
54macro_rules! assert_eq_excluding {
55    ($actual:expr, $expect:expr, $($field:ident),+) => {{
56        let mut actual_clone = $actual.clone();
57        $(
58            paste! {
59                let value = $expect.[<$field>]().clone();
60                actual_clone.[<set_ $field>](value);
61            }
62        )+
63
64        let fields = vec![
65            $(
66                stringify!($field),
67            )+
68        ];
69        let description = $crate::impls::assert_eq_excluding::description(None, fields);
70
71        assert_eq!(actual_clone, $expect, "{}", description);
72    }};
73}
74
75pub fn description(case_name: Option<&str>, fields: Vec<&str>) -> String {
76    let verb = verb(fields.len());
77    let fields = fields_string(fields);
78
79    if let Some(case_name) = case_name {
80        format!(
81            "{} (Fields {} {} updated by default)",
82            case_name, fields, verb
83        )
84    } else {
85        format!("(Fields {} {} updated by default)", fields, verb)
86    }
87}
88
89fn verb(field_size: usize) -> String {
90    if field_size == 1 {
91        "is".to_string()
92    } else {
93        "are".to_string()
94    }
95}
96
97fn fields_string(fields: Vec<&str>) -> String {
98    let field_size = fields.len();
99
100    let fields = fields
101        .into_iter()
102        .map(|s| format!("`{}`", s))
103        .collect::<Vec<String>>();
104
105    let fields = match field_size {
106        0 => "".to_string(),
107        1 => fields[0].clone(),
108        2 => format!("{} and {}", fields[0], fields[1]),
109        _ => {
110            let (head, tail) = fields.split_at(fields.len() - 1);
111            let formatted_tail = tail.join(", ");
112            format!("{} and {}", formatted_tail, head.last().unwrap())
113        }
114    };
115
116    fields
117}