4 releases

0.1.3 Feb 27, 2023
0.1.2 Feb 23, 2023
0.1.1 Feb 23, 2023
0.1.0 Feb 22, 2023

#783 in Development tools

Download history 234/week @ 2024-07-29 207/week @ 2024-08-05 210/week @ 2024-08-12 114/week @ 2024-08-19 226/week @ 2024-08-26 255/week @ 2024-09-02 110/week @ 2024-09-09 74/week @ 2024-09-16 153/week @ 2024-09-23 225/week @ 2024-09-30 181/week @ 2024-10-07 154/week @ 2024-10-14 225/week @ 2024-10-21 283/week @ 2024-10-28 195/week @ 2024-11-04 246/week @ 2024-11-11

954 downloads per month
Used in 2 crates (via pagefind)

MIT/Apache

10KB

rust-patch

Patch structs with other structs

Build status Crates.io Documentation

rust-patch allows you to avoid boilerplate code when implementing partial updates of Rust structs.
Simply define a patch struct containing a subset of your fields, derive the Patch trait, and specify the original struct using the #[patch] attribute.
Fields of a patch struct may either be of the same type T as in the original struct or Option<T>.
In the latter case, the field to be patched will be left unchanged if the corresponding field in the patch is None

This crate is no_std compatible.

Container attributes

#[patch = "..."]

Set target struct to be patched

use rust_patch::Patch;
struct Item { data: u32 }

#[derive(Patch)]
#[patch = "Item"]
struct ItemPatch { data: Option<u32> }

Field attributes

By default, any fields in the patch of type Option<T> will be applied as such:

if let Some(val) = patch.field {
    target.field = val;
} 

this behavior can be changed by the following field attributes.

#[patch(as_option)]

The as_option attribute allows patching structs where a field itself is already an Option<T> with the following logic:

if patch.field.is_some() {
    target.field = patch.field;
}

Applying this attribute to a field with a type without an is_some() method results in an error.

#[patch(direct)]

The direct attribute makes it so that the field is treated like any other T, meaning it will be applied like this:

target.field = patch.field;

Applying this attribute to a field where the type is not Option<T> is a no-op.

Example

use rust_patch::Patch;
use serde::Deserialize;

#[derive(PartialEq, Debug)]
struct User {
    id: String,
    name: String,
    email: String,
}

#[derive(Deserialize, Patch)]
#[patch = "User"]
struct UserPatch {
    name: Option<String>,
    email: Option<String>,
}

let user = User {
    id: "6bf25b70-bffa-49e0-905b-2d2e608e3abd".to_string(),
    name: "Max Mustermann".to_string(),
    email: "max.mustermann@example.org".to_string(),
};

let raw_patch = r#"{
    "id": "some invalid id",
    "email": "max.mustermann@example.com"
}"#;

let patch: UserPatch = serde_json::from_str(raw_patch).unwrap();
let patched_user = patch.apply(user);

// Since `id` is not part of our `UserPatch` struct it stays unchanged
assert_eq! {
    patched_user,
    User {
        id: "6bf25b70-bffa-49e0-905b-2d2e608e3abd".to_string(),
        name: "Max Mustermann".to_string(),
        email: "max.mustermann@example.com".to_string()
    }
};

Dependencies

~1.5MB
~38K SLoC