Generalise compare_fields to work with iterators (#4823)
## Proposed Changes Add `compare_fields(as_iter)` as a field attribute to `compare_fields_derive`. This allows any iterable type to be compared in the same as a slice (by index). This is forwards-compatible with tree-states types like `List` and `Vector` which can not be cast to slices.
This commit is contained in:
parent
1b4545cd9d
commit
463e62e833
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1120,6 +1120,7 @@ name = "compare_fields"
|
|||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compare_fields_derive",
|
"compare_fields_derive",
|
||||||
|
"itertools",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4,6 +4,9 @@ version = "0.2.0"
|
|||||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
compare_fields_derive = { workspace = true }
|
compare_fields_derive = { workspace = true }
|
||||||
|
|
||||||
|
@ -81,11 +81,8 @@
|
|||||||
//! }
|
//! }
|
||||||
//! ];
|
//! ];
|
||||||
//! assert_eq!(bar_a.compare_fields(&bar_b), bar_a_b);
|
//! assert_eq!(bar_a.compare_fields(&bar_b), bar_a_b);
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! // TODO:
|
|
||||||
//! ```
|
//! ```
|
||||||
|
use itertools::{EitherOrBoth, Itertools};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
@ -112,13 +109,38 @@ impl Comparison {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_slice<T: Debug + PartialEq<T>>(field_name: String, a: &[T], b: &[T]) -> Self {
|
pub fn from_slice<T: Debug + PartialEq<T>>(field_name: String, a: &[T], b: &[T]) -> Self {
|
||||||
let mut children = vec![];
|
Self::from_iter(field_name, a.iter(), b.iter())
|
||||||
|
|
||||||
for i in 0..std::cmp::max(a.len(), b.len()) {
|
|
||||||
children.push(FieldComparison::new(format!("{i}"), &a.get(i), &b.get(i)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::parent(field_name, a == b, children)
|
pub fn from_into_iter<'a, T: Debug + PartialEq + 'a>(
|
||||||
|
field_name: String,
|
||||||
|
a: impl IntoIterator<Item = &'a T>,
|
||||||
|
b: impl IntoIterator<Item = &'a T>,
|
||||||
|
) -> Self {
|
||||||
|
Self::from_iter(field_name, a.into_iter(), b.into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_iter<'a, T: Debug + PartialEq + 'a>(
|
||||||
|
field_name: String,
|
||||||
|
a: impl Iterator<Item = &'a T>,
|
||||||
|
b: impl Iterator<Item = &'a T>,
|
||||||
|
) -> Self {
|
||||||
|
let mut children = vec![];
|
||||||
|
let mut all_equal = true;
|
||||||
|
|
||||||
|
for (i, entry) in a.zip_longest(b).enumerate() {
|
||||||
|
let comparison = match entry {
|
||||||
|
EitherOrBoth::Both(x, y) => {
|
||||||
|
FieldComparison::new(format!("{i}"), &Some(x), &Some(y))
|
||||||
|
}
|
||||||
|
EitherOrBoth::Left(x) => FieldComparison::new(format!("{i}"), &Some(x), &None),
|
||||||
|
EitherOrBoth::Right(y) => FieldComparison::new(format!("{i}"), &None, &Some(y)),
|
||||||
|
};
|
||||||
|
all_equal = all_equal && comparison.equal();
|
||||||
|
children.push(comparison);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::parent(field_name, all_equal, children)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain_children<F>(&mut self, f: F)
|
pub fn retain_children<F>(&mut self, f: F)
|
||||||
|
@ -4,10 +4,11 @@ use proc_macro::TokenStream;
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
fn is_slice(field: &syn::Field) -> bool {
|
fn is_iter(field: &syn::Field) -> bool {
|
||||||
field.attrs.iter().any(|attr| {
|
field.attrs.iter().any(|attr| {
|
||||||
attr.path.is_ident("compare_fields")
|
attr.path.is_ident("compare_fields")
|
||||||
&& attr.tokens.to_string().replace(' ', "") == "(as_slice)"
|
&& (attr.tokens.to_string().replace(' ', "") == "(as_slice)"
|
||||||
|
|| attr.tokens.to_string().replace(' ', "") == "(as_iter)")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,13 +35,13 @@ pub fn compare_fields_derive(input: TokenStream) -> TokenStream {
|
|||||||
let field_name = ident_a.to_string();
|
let field_name = ident_a.to_string();
|
||||||
let ident_b = ident_a.clone();
|
let ident_b = ident_a.clone();
|
||||||
|
|
||||||
let quote = if is_slice(field) {
|
let quote = if is_iter(field) {
|
||||||
quote! {
|
quote! {
|
||||||
comparisons.push(compare_fields::Comparison::from_slice(
|
comparisons.push(compare_fields::Comparison::from_into_iter(
|
||||||
#field_name.to_string(),
|
#field_name.to_string(),
|
||||||
&self.#ident_a,
|
&self.#ident_a,
|
||||||
&b.#ident_b)
|
&b.#ident_b
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
|
Loading…
Reference in New Issue
Block a user