sharksforarms / deku

Declarative binary reading and writing: bit-level, symmetric, serialization/deserialization
Apache License 2.0
1.05k stars 54 forks source link

Conditional DekuWrite #403

Closed whati001 closed 5 months ago

whati001 commented 5 months ago

Hi all,

would it be possible to add support for conditional write? For example, I would like to skip the serialization of specific fields based on there value as explained in the example below:

Struct to serialize into binary:

#[derive(Debug, Serialize, DekuWrite)]
pub struct StructEntry<T>
where
    T: DekuWrite,
{
    pub fieldA : u8,
    pub fieldB : T,
    #[deku(skip)]
    pub serialize: bool,
}

pub struct StructType {
    #[deku(cond = "self.serialize == true")]
    pub entryA: StructEntry<u32>,
    #[deku(cond = "self.serialize == true")]
    pub entryB: StructEntry<u32>,

And now, I would like to skip the serialization based on the serialize flag.

let mut config = StructType::Default; // assumed that Default is implemented
config.fieldA.serialize = false;
let bytes = StructType::to_bytes(&config).unwrap();

I have tried to use #[serde(skip_serializing_if = .., but this has not worked for me.

wcampbell0x2a commented 5 months ago

Deku does not have a skip_serializing_if attribute, but could add this.

sharksforarms commented 5 months ago

@whati001 I believe by combining skip and cond you can achieve what you're looking for?

You're right there isn't something like this for 'write side only' or 'read side only'

You can interpret it as "skip field (read or write) if cond"

Here's an example.

use deku::prelude::*;

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
struct MyStruct {
    should_next: bool,

    #[deku(skip, cond = "!*should_next")]
    // on read: if cond == true, we skip and the value is set to Default::default()
    // on write: if cond == true, we skip and the writing of the value
    optional: Option<u8>,

    other_field: u8,
}

fn main() {
    env_logger::init();

    {
        let input = &[0x01, 0x02, 0x03];
        let (_rest, r) = MyStruct::from_bytes((input, 0)).unwrap();
        assert_eq!(
            r,
            MyStruct {
                should_next: true,
                optional: Some(0x02),
                other_field: 0x03,
            }
        );

        assert_eq!(vec![0x01, 0x02, 0x03], r.to_bytes().unwrap())
    }

    {
        let input = &[0x00, 0x03];
        let (_rest, r) = MyStruct::from_bytes((input, 0)).unwrap();
        assert_eq!(
            r,
            MyStruct {
                should_next: false,
                optional: None,
                other_field: 0x03,
            }
        );

        assert_eq!(vec![0x00, 0x03], r.to_bytes().unwrap())
    }
}
whati001 commented 5 months ago

Hi @sharksforarms Thank you very much for the quick feedback. You are right, seems like I have overseen to add the skip attribute. Using the following deku #[deku(skip, cond = '...'] works now for me.