Skip to content

Commit

Permalink
feat: Merge trait to merge patches (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
taorepoara authored Aug 18, 2024
1 parent 12c48ee commit 3e98654
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ jobs:
nix develop -c cargo run --features=std --example option
nix develop -c cargo test --features=std
- name: Test with merge features
run: |
nix develop -c cargo run --features=option --features=merge --example option
nix develop -c cargo run --features=merge --example op
nix develop -c cargo test --features=merge
- name: Test with option features
run: |
nix develop -c cargo run --features=none_as_default --example option
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ The [examples][examples] demo following scenarios.
This crate also includes the following optional features:
- `status`(default): implements the `PatchStatus` trait for the patch struct, which provides the `is_empty` method.
- `op` (default): provide operators `<<` for instance and patch, and `+` for patches
- `merge` (optional): implements the `Merge` trait for the patch struct, which provides the `merge` method.
- `std`(optional):
- `box`: implements the `Patch<Box<P>>` trait for `T` where `T` implements `Patch<P>`.
This let you patch a boxed (or not) struct with a boxed patch.
Expand Down
1 change: 1 addition & 0 deletions struct-patch-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ syn = { version = "2.0", features = ["parsing"] }
[features]
status = []
op = []
merge = []

[dev-dependencies]
pretty_assertions_sorted = "1.2.3"
37 changes: 28 additions & 9 deletions struct-patch-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,29 @@ impl Patch {
#[cfg(not(feature = "status"))]
let patch_status_impl = quote!();

#[cfg(feature = "merge")]
let patch_merge_impl = quote!(
impl #generics struct_patch::traits::Merge for #name #generics #where_clause {
fn merge(self, other: Self) -> Self {
Self {
#(
#renamed_field_names: match (self.#renamed_field_names, other.#renamed_field_names) {
(Some(a), Some(b)) => Some(a.merge(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
},
)*
#(
#original_field_names: other.#original_field_names.or(self.#original_field_names),
)*
}
}
}
);
#[cfg(not(feature = "merge"))]
let patch_merge_impl = quote!();

#[cfg(feature = "op")]
let op_impl = quote! {
impl #generics core::ops::Shl<#name #generics> for #struct_name #generics #where_clause {
Expand All @@ -111,18 +134,12 @@ impl Patch {
}
}

#[cfg(feature = "merge")]
impl #generics core::ops::Shl<#name #generics> for #name #generics #where_clause {
type Output = Self;

fn shl(mut self, rhs: #name #generics) -> Self {
Self {
#(
#renamed_field_names: rhs.#renamed_field_names.or(self.#renamed_field_names),
)*
#(
#original_field_names: rhs.#original_field_names.or(self.#original_field_names),
)*
}
fn shl(mut self, rhs: Self) -> Self {
struct_patch::traits::Merge::merge(self, rhs)
}
}

Expand Down Expand Up @@ -222,6 +239,8 @@ impl Patch {

#patch_status_impl

#patch_merge_impl

#patch_impl

#op_impl
Expand Down
3 changes: 3 additions & 0 deletions struct-patch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ status = [
op = [
"struct-patch-derive/op"
]
merge = [
"struct-patch-derive/merge"
]

std = ["box", "option"]
box = []
Expand Down
14 changes: 10 additions & 4 deletions struct-patch/examples/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,18 @@ fn main() {
// Will be handdled as the discussion
// https://github.com/yanganto/struct-patch/pull/32#issuecomment-2283154990

let final_item_with_bracket = item.clone() << (conflict_patch.clone() << the_other_patch.clone());
let final_item_without_bracket = item.clone() << conflict_patch << the_other_patch.clone();
assert_eq!(final_item_with_bracket, final_item_without_bracket);
assert_eq!(final_item_with_bracket.field_int, 2);
let final_item_without_bracket =
item.clone() << conflict_patch.clone() << the_other_patch.clone();
assert_eq!(final_item_without_bracket.field_int, 2);

#[cfg(feature = "merge")]
{
let final_item_with_bracket =
item.clone() << (conflict_patch.clone() << the_other_patch.clone());
assert_eq!(final_item_with_bracket, final_item_without_bracket);
assert_eq!(final_item_with_bracket.field_int, 2);
}

let final_item_from_merge = item.clone() << (another_patch.clone() + the_other_patch.clone());
assert_eq!(final_item_from_merge.field_string, "from another patch");
assert_eq!(final_item_from_merge.field_complete, true);
Expand Down
98 changes: 97 additions & 1 deletion struct-patch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub use traits::*;
#[cfg(test)]
mod tests {
use serde::Deserialize;
#[cfg(feature = "merge")]
use struct_patch::Merge;
use struct_patch::Patch;
#[cfg(feature = "status")]
use struct_patch::PatchStatus;
Expand Down Expand Up @@ -310,7 +312,7 @@ mod tests {
);
}

#[cfg(feature = "op")]
#[cfg(all(feature = "op", feature = "merge"))]
#[test]
fn test_shl_on_patch() {
#[derive(Patch, Debug, PartialEq)]
Expand Down Expand Up @@ -385,4 +387,98 @@ mod tests {
let patch2 = ItemPatch { field: Some(2) };
let _overall_patch = patch + patch2;
}

#[cfg(feature = "merge")]
#[test]
fn test_merge() {
#[derive(Patch)]
#[patch(attribute(derive(PartialEq, Debug)))]
struct Item {
a: u32,
b: u32,
c: u32,
d: u32,
}

let patch = ItemPatch {
a: None,
b: Some(2),
c: Some(0),
d: None,
};
let patch2 = ItemPatch {
a: Some(1),
b: None,
c: Some(3),
d: None,
};

let merged_patch = patch.merge(patch2);
assert_eq!(
merged_patch,
ItemPatch {
a: Some(1),
b: Some(2),
c: Some(3),
d: None,
}
);
}

#[cfg(feature = "merge")]
#[test]
fn test_merge_nested() {
#[derive(Patch, PartialEq, Debug)]
#[patch(attribute(derive(PartialEq, Debug, Clone)))]
struct B {
c: u32,
d: u32,
e: u32,
f: u32,
}

#[derive(Patch)]
#[patch(attribute(derive(PartialEq, Debug)))]
struct A {
a: u32,
#[patch(name = "BPatch")]
b: B,
}

let patches = vec![
APatch {
a: Some(1),
b: Some(BPatch {
c: None,
d: Some(2),
e: Some(0),
f: None,
}),
},
APatch {
a: Some(0),
b: Some(BPatch {
c: Some(1),
d: None,
e: Some(3),
f: None,
}),
},
];

let merged_patch = patches.into_iter().reduce(Merge::merge).unwrap();

assert_eq!(
merged_patch,
APatch {
a: Some(0),
b: Some(BPatch {
c: Some(1),
d: Some(2),
e: Some(3),
f: None,
}),
}
);
}
}
20 changes: 20 additions & 0 deletions struct-patch/src/std.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(all(feature = "merge", feature = "option"))]
use crate::Merge;
#[cfg(any(feature = "box", feature = "option"))]
use crate::Patch;
#[cfg(feature = "box")]
Expand Down Expand Up @@ -139,6 +141,24 @@ where
}
}

#[cfg(all(feature = "option", feature = "merge"))]
impl<T> Merge for Option<T>
where
T: Merge,
{
fn merge(self, other: Self) -> Self {
if let Some(other) = other {
if self.is_some() {
Some(self.unwrap().merge(other))
} else {
Some(other)
}
} else {
None
}
}
}

#[cfg(test)]
mod tests {
use crate as struct_patch;
Expand Down
6 changes: 6 additions & 0 deletions struct-patch/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,9 @@ pub trait PatchStatus {
/// Returns `true` if all fields are `None`, `false` otherwise.
fn is_empty(&self) -> bool;
}

#[cfg(feature = "merge")]
/// A patch struct that can be merged to another one
pub trait Merge {
fn merge(self, other: Self) -> Self;
}

0 comments on commit 3e98654

Please sign in to comment.