#[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_traitsparse_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> {
...
}