Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Merge trait to merge patches #45

Merged
merged 1 commit into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}