Skip to content

Commit d7ba2b9

Browse files
authored
ordered collection strategies (#29)
* generic levenshtein impl * integrate levenshtein difference as the ordered_array_like collection strategy
1 parent 9cf7f83 commit d7ba2b9

File tree

8 files changed

+682
-17
lines changed

8 files changed

+682
-17
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "structdiff"
3-
version = "0.6.2"
3+
version = "0.6.3"
44
edition = "2021"
55
license = "Apache-2.0 OR MIT"
66
repository = "https://github.com/knickish/structdiff"
@@ -12,7 +12,7 @@ categories = ["compression"]
1212
nanoserde = { version = "^0.1.37", optional = true }
1313
rustc-hash = { version = "1.1.0", optional = true }
1414
serde = { version = "^1.0.0", optional = true, features = ["derive"] }
15-
structdiff-derive = { path = "derive", version = "=0.6.2" }
15+
structdiff-derive = { path = "derive", version = "=0.6.3" }
1616

1717
[features]
1818
"default" = []

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ For more examples take a look at [integration tests](/tests)
5050
## Derive macro attributes
5151
- `#[difference(skip)]` - Do not consider this field when creating a diff
5252
- `#[difference(recurse)]` - Generate a StructDiff for this field when creating a diff
53-
- `#[difference(collection_strategy = {})]`
54-
- `"unordered_array_like"` - Generates a changeset for array-like collections of items which implement `Hash + Eq`, rather than cloning the entire list. (currently works for `Vec`, `BTreeSet`, `LinkedList`, and `HashSet`)
55-
- `"unordered_map_like"` - Generates a changeset for map-like collections for which the key implements `Hash + Eq`, rather than cloning the entire map. (currently works for `HashMap` and `BTreeMap`)
56-
- `#[difference(map_equality = {})]` - Used with `unordered_map_like`, specifies whether the equality check should consider keys only, or both keys and values
53+
- `#[difference(collection_strategy = {})]`
54+
- `"ordered_array_like"` - Generates a minimal changeset for ordered, array-like collections of items which implement `PartialEq`. (uses levenshtein difference)
55+
- `"unordered_array_like"` - Generates a minimal changeset for unordered, array-like collections of items which implement `Hash + Eq`.
56+
- `"unordered_map_like"` - Generates a minimal changeset for unordered, map-like collections for which the key implements `Hash + Eq`.
57+
- `#[difference(map_equality = {})]` - Used with `unordered_map_like`
5758
- `"key_only"` - only replace a key-value pair for which the key has changed
5859
- `"key_and_value"` - replace a key-value pair if either the key or value has changed
5960
- `#[difference(setters)]` - Generate setters for all fields in the struct (used on struct)

derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "structdiff-derive"
3-
version = "0.6.2"
3+
version = "0.6.3"
44
authors = ["Makepad <[email protected]>", "Fedor <[email protected]>", "Kirk <[email protected]"]
55
edition = "2018"
66
description = "derive macro library for structdiff"

derive/src/difference.rs

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
482482
l!(diff_ref_body, "{}", diff_body_fragment_ref);
483483
},
484484
(true, Some(strat), false) => match strat {
485+
crate::shared::CollectionStrategy::OrderedArrayLike => { panic!("Recursion inside of array-like collections is not yet supported"); },
485486
crate::shared::CollectionStrategy::UnorderedArrayLikeHash => { panic!("Recursion inside of array-like collections is not yet supported"); },
486487
crate::shared::CollectionStrategy::UnorderedMapLikeHash(crate::shared::MapStrategy::KeyAndValue) => {
487488
let generic_names = field.ty.wraps.as_ref().map(|x| x.iter().map(|y| y.full().clone()).collect::<Vec<_>>()).expect("Missing types for map creation").join(",");
@@ -581,13 +582,17 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
581582
(true, (_, false, Some(name_override))) | (false, (true, false, Some(name_override))) => {
582583
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", name_override);
583584
l!(setters_body, "\npub fn {}(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", name_override, field.ty.full());
584-
l!(setters_body, "\n\tstructdiff::collections::unordered_map_like_recursive::unordered_hashcmp(self.{}.iter(), value.iter(), true)", field_name);
585+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_map_like_recursive::unordered_hashcmp(self.{}.iter(), value.iter(), true);", field_name);
586+
l!(setters_body, "\n\tself.{} = value;", field_name);
587+
l!(setters_body, "\n\tret");
585588
l!(setters_body, "\n}");
586589
},
587590
(true, (_, false, None)) | (false, (true, false, None)) => {
588591
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", field_name);
589592
l!(setters_body, "\npub fn set_{}_with_diff(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", field_name, field.ty.full());
590-
l!(setters_body, "\n\tstructdiff::collections::unordered_map_like_recursive::unordered_hashcmp(self.{}.iter(), value.iter(), true)", field_name);
593+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_map_like_recursive::unordered_hashcmp(self.{}.iter(), value.iter(), true);", field_name);
594+
l!(setters_body, "\n\tself.{} = value;", field_name);
595+
l!(setters_body, "\n\tret");
591596
l!(setters_body, "\n}");
592597
},
593598
_ => ()
@@ -599,6 +604,74 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
599604
},
600605
(false, Some(_), true) => panic!("Collection strategies inside of options are not yet supported"),
601606
(false, Some(strat), false) => match strat {
607+
crate::shared::CollectionStrategy::OrderedArrayLike => {
608+
let generic_ref_names = field.ty.wraps.as_ref().map(|x| x.iter().map(|y| y.full()).collect::<Vec<_>>()).expect("Missing types for map creation").join(", &'__diff_target ");
609+
l!(diff_enum_body, " {}(structdiff::collections::ordered_array_like::OrderedArrayLikeDiffOwned<{}>),", field_name, field.ty.wraps.as_ref().expect("Using collection strategy on a non-collection")[0].full());
610+
l!(diff_ref_enum_body, " {}(structdiff::collections::ordered_array_like::OrderedArrayLikeDiffRef<'__diff_target, {}>),", field_name, generic_ref_names);
611+
612+
l!(
613+
apply_single_body,
614+
"Self::Diff::{}(__{}) => self.{} = structdiff::collections::ordered_array_like::apply(__{}, std::mem::take(&mut self.{})).collect(),",
615+
field_name,
616+
index,
617+
field_name,
618+
index,
619+
field_name
620+
);
621+
622+
l!(
623+
diff_body,
624+
"if let Some(list_diffs) = structdiff::collections::ordered_array_like::levenshtein(&updated.{}, &self.{}) {{
625+
diffs.push(Self::Diff::{}(list_diffs.into()));
626+
}};"
627+
,
628+
field_name,
629+
field_name,
630+
field_name
631+
);
632+
633+
l!(
634+
diff_ref_body,
635+
"if let Some(list_diffs) = structdiff::collections::ordered_array_like::levenshtein(&updated.{}, &self.{}) {{
636+
diffs.push(Self::DiffRef::{}(list_diffs));
637+
}};"
638+
,
639+
field_name,
640+
field_name,
641+
field_name
642+
);
643+
644+
l!(
645+
ref_into_owned_body,
646+
"\t {}Ref::{}(v) => {}::{}(v.into()),",
647+
enum_name,
648+
field_name,
649+
enum_name,
650+
field_name
651+
);
652+
653+
#[cfg(feature = "generated_setters")]
654+
match (all_setters, attrs_setter(&field.attributes)) {
655+
(_, (_, true, _)) => (),
656+
(true, (_, false, Some(name_override))) | (false, (true, false, Some(name_override))) => {
657+
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", name_override);
658+
l!(setters_body, "\npub fn {}(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", name_override, field.ty.full());
659+
l!(setters_body, "\n\tlet ret = structdiff::collections::ordered_array_like::levenshtein(&value, &self.{}).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
660+
l!(setters_body, "\n\tself.{} = value;", field_name);
661+
l!(setters_body, "\n\tret");
662+
l!(setters_body, "\n}");
663+
},
664+
(true, (_, false, None)) | (false, (true, false, None)) => {
665+
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", field_name);
666+
l!(setters_body, "\npub fn set_{}_with_diff(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", field_name, field.ty.full());
667+
l!(setters_body, "\n\tlet ret = structdiff::collections::ordered_array_like::levenshtein(&value, &self.{}).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
668+
l!(setters_body, "\n\tself.{} = value;", field_name);
669+
l!(setters_body, "\n\tret");
670+
l!(setters_body, "\n}");
671+
},
672+
_ => ()
673+
};
674+
}
602675
crate::shared::CollectionStrategy::UnorderedArrayLikeHash => {
603676
let generic_ref_names = field.ty.wraps.as_ref().map(|x| x.iter().map(|y| y.full()).collect::<Vec<_>>()).expect("Missing types for map creation").join(", &'__diff_target ");
604677
l!(diff_enum_body, " {}(structdiff::collections::unordered_array_like::UnorderedArrayLikeDiff<{}>),", field_name, field.ty.wraps.as_ref().expect("Using collection strategy on a non-collection")[0].full());
@@ -651,13 +724,17 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
651724
(true, (_, false, Some(name_override))) | (false, (true, false, Some(name_override))) => {
652725
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", name_override);
653726
l!(setters_body, "\npub fn {}(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", name_override, field.ty.full());
654-
l!(setters_body, "\n\tstructdiff::collections::unordered_array_like::unordered_hashcmp(self.{}.iter(), value.iter()).map(|x| <Self as StructDiff>::Diff::{}(x.into()))", field_name, field_name);
727+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_array_like::unordered_hashcmp(self.{}.iter(), value.iter()).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
728+
l!(setters_body, "\n\tself.{} = value;", field_name);
729+
l!(setters_body, "\n\tret");
655730
l!(setters_body, "\n}");
656731
},
657732
(true, (_, false, None)) | (false, (true, false, None)) => {
658733
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", field_name);
659734
l!(setters_body, "\npub fn set_{}_with_diff(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", field_name, field.ty.full());
660-
l!(setters_body, "\n\tstructdiff::collections::unordered_array_like::unordered_hashcmp(self.{}.iter(), value.iter()).map(|x| <Self as StructDiff>::Diff::{}(x.into()))", field_name, field_name);
735+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_array_like::unordered_hashcmp(self.{}.iter(), value.iter()).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
736+
l!(setters_body, "\n\tself.{} = value;", field_name);
737+
l!(setters_body, "\n\tret");
661738
l!(setters_body, "\n}");
662739
},
663740
_ => ()
@@ -717,13 +794,17 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
717794
(true, (_, false, Some(name_override))) | (false, (true, false, Some(name_override))) => {
718795
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", name_override);
719796
l!(setters_body, "\npub fn {}(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", name_override, field.ty.full());
720-
l!(setters_body, "\n\tstructdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), true).map(|x| <Self as StructDiff>::Diff::{}(x.into()))", field_name, field_name);
797+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), true).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
798+
l!(setters_body, "\n\tself.{} = value;", field_name);
799+
l!(setters_body, "\n\tret");
721800
l!(setters_body, "\n}");
722801
},
723802
(true, (_, false, None)) | (false, (true, false, None)) => {
724803
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", field_name);
725804
l!(setters_body, "\npub fn set_{}_with_diff(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", field_name, field.ty.full());
726-
l!(setters_body, "\n\tstructdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), true).map(|x| <Self as StructDiff>::Diff::{}(x.into()))", field_name, field_name);
805+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), true).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
806+
l!(setters_body, "\n\tself.{} = value;", field_name);
807+
l!(setters_body, "\n\tret");
727808
l!(setters_body, "\n}");
728809
},
729810
_ => ()
@@ -782,13 +863,17 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
782863
(true, (_, false, Some(name_override))) | (false, (true, false, Some(name_override))) => {
783864
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", name_override);
784865
l!(setters_body, "\npub fn {}(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", name_override, field.ty.full());
785-
l!(setters_body, "\n\tstructdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), false).map(|x| <Self as StructDiff>::Diff::{}(x.into()))", field_name, field_name);
866+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), false).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
867+
l!(setters_body, "\n\tself.{} = value;", field_name);
868+
l!(setters_body, "\n\tret");
786869
l!(setters_body, "\n}");
787870
},
788871
(true, (_, false, None)) | (false, (true, false, None)) => {
789872
l!(setters_body, "\n/// Setter generated by StructDiff. Use to set the {} field and generate a diff if necessary", field_name);
790873
l!(setters_body, "\npub fn set_{}_with_diff(&mut self, value: {}) -> Option<<Self as StructDiff>::Diff> {{", field_name, field.ty.full());
791-
l!(setters_body, "\n\tstructdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), false).map(|x| <Self as StructDiff>::Diff::{}(x.into()))", field_name, field_name);
874+
l!(setters_body, "\n\tlet ret = structdiff::collections::unordered_map_like::unordered_hashcmp(self.{}.iter(), value.iter(), false).map(|x| <Self as StructDiff>::Diff::{}(x.into()));", field_name, field_name);
875+
l!(setters_body, "\n\tself.{} = value;", field_name);
876+
l!(setters_body, "\n\tret");
792877
l!(setters_body, "\n}");
793878
},
794879
_ => ()

derive/src/shared.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub enum MapStrategy {
1717

1818
#[derive(Debug)]
1919
pub enum CollectionStrategy {
20+
OrderedArrayLike,
2021
UnorderedArrayLikeHash,
2122
UnorderedMapLikeHash(MapStrategy),
2223
}
@@ -66,6 +67,7 @@ pub fn attrs_collection_type(attributes: &[crate::parse::Attribute]) -> Option<C
6667
attributes.iter().find_map(|attr| {
6768
if attr.tokens.len() == 2 && attr.tokens[0] == "collection_strategy" {
6869
let strategy = match attr.tokens[1].clone().as_str() {
70+
"ordered_array_like" => CollectionStrategy::OrderedArrayLike,
6971
"unordered_array_like" => CollectionStrategy::UnorderedArrayLikeHash,
7072
"unordered_map_like" => {
7173
let map_compare_type = attrs_map_strategy(attributes).unwrap_or_default();

src/collections/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
pub mod unordered_array_like;
22
pub mod unordered_map_like;
33
pub mod unordered_map_like_recursive;
4+
5+
pub mod ordered_array_like;

0 commit comments

Comments
 (0)