Attribute Macro enum_parse

Source
#[enum_parse]
Expand description

Procedural macro used on enums to provide a parse() method that deserializes given input data into one of the enum variants:

Self::parse(input: T, id: usize) -> Option<Self>;

The specific variant is chosen by numeric index (usize), which needs to be assigned to every variant with a custom attribute: #[attr(ID = 0xABCD)].

This macro doesn’t define the means of deserialization. It has to be provided by the user, most likely from another crate.

§Examples

(SomehowParsable is an imaginary derive that implements a trait)

use enum_parse::enum_parse;

#[enum_parse(derive(SomehowParsable, Debug, Default),
             repr(C, packed),
             attr(parse_input = &[u8], parse_fn = somehow_parse))]
pub enum Payload {
    #[attr(ID = 0x2b)]
    Hello { a: u8, b: u64, c: u64, d: u8 },
    #[attr(ID = 0x42)]
    Goodbye { a: u8, e: u8 },
    #[attr(ID = _)]
    Unknown,
}

pub fn parse_packet(data: &[u8]) -> Option<Payload> {
    let id: usize = data[0] as usize;
    // parse method is generated by the macro
    Payload::parse(&data[1..], id)
}

(This example expects SomehowParsable to provide a somehow_parse function that accepts a single &[u8] parameter)

To achieve this goal, this macro generates a structure for each enum variant, then modifies the enum variants to contain those structure instead. All attributes provided to #[enum_parse] are passed to those generated structures with the exception of attr(…) attribute, which is parsed internally by #[enum_parse].

The enum_parse(..., attr(...)) attribute can define the following properties:

  • parse_fn = name_of_function_implemented_by_derive_traits
  • parse_input = data_type_accepted_by_the above_function

The per-veriant #[attr(...)] attribute can currently define only the ID of the variant. Additional attributes to enum variants are currently not supported.

The #[attr(ID = ...)] attribute can define a numerical index, or any other const-evaluated expression, e.g:

#[attr(ID = HELLO_PACKET_ID)]

where HELLO_PACKET_ID is defined as:

const HELLO_PACKET_ID: usize = 0xAA;

The #[attr(ID = ...)] attribute can be also a special _ character that is used to match-all in the parse method:

#[attr(ID = _)]
Unknown,

or

#[attr(ID = _)]
Unknown { some_always_present_byte: u8 },

If the match-all variant contains no fields, the parse() function will always return it upon matching unknown id. If the variant contains some fields, the parse() function will attempt to call parse_fn on it.

For the original example code, the expanded version would be:

pub enum Payload {
    Hello(Hello),
    Goodbye(Goodbye),
    Unknown,
}

impl Payload {
    pub fn parse(data: &[u8], id: usize) -> Option<Self> {
        match id {
            Hello::ID => Hello::read_from(data).map(|s| Self::Hello(s)),
            Goodbye::ID => Goodbye::read_from(data).map(|s| Self::Goodbye(s)),
            _ => Some(Self::Unknown),
        }
    }
}

#[derive(SomehowParsable, Debug, Default)]
#[repr(C, packed)]
pub struct Hello {
    pub a: u8,
    pub b: u64,
    pub c: u64,
    pub d: u8,
}
impl Hello {
    pub const ID: usize = 0x2b;
}

#[derive(SomehowParsable, Debug, Default)]
#[repr(C, packed)]
pub struct Goodbye {
    pub a: u8,
    pub e: u8,
}
impl Goodbye {
    pub const ID: usize = 0x42;
}

#[derive(SomehowParsable, Debug, Default)]
#[repr(C, packed)]
pub struct Unknown {}

pub fn parse_packet(data: &[u8]) -> Option<Payload> {
    ...
}