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}