A Rust crate for simplifying the process of parsing binary file data into various Rust data types using the TypeCast macro.
TODO: - Create Tests - Better Error Handling
The macro is designed to simplify parsing data records in a binary file via usage of a text from a description file, in the example, a desc.xml
file. The following demonstrates how to define a custom enum called DataTypes
and use the TypeCast
macro.
In this example define DataTypes
, and use the cast attribute for each variant.
```rust
pub enum DataTypes { #[cast(fromlebytes => f32)] AnyCustomVariant, #[cast(fromlebytes => [f32;2])] AnyCustomVariant2
} ```
This will automatically generate a DataTypesCast enum:
```rust
pub enum DataTypesCast { AnyCustomVariant(f32), AnyCustomVariant2([f32;2]), } ```
Any Enum that utilizes the TypeCast will simply have Cast
appended to the name. This:
```rust
enum ExampleEnum {
//
}
will generate the following at compile time:
rust
enum ExampleEnumCast {
//
}
``
The attribute macro
#[derive(Clone, Debug, Serialize, Deserialize)]is hardcoded above the
enum {}Cast` at the moment. If there is significant demand, the macro can be altered to include only those defined on the parent enum.
Okay, but why not just define the DataTypesCast or ExampleEnumCast and skip the attribute nonsense?
Because the following are automatically generated:
```rust
impl DataTypes {
pub fn parse(self, input: &mut &[u8]) -> DataTypesCast {
match self {
DataTypes::AnyCustomVariant => {
DataTypesCast::AnyCustomVariant({
let (bytes, ) = input.splitat(std::mem::sizeof::
// The TryInto implementations are so the values can be used outside of match statements. Right now, its tedious to use, but it works. See the hashmapped_fields example for usage.
impl std::convert::TryInto<f32> for DataTypesCast {
type Error = String;
fn try_into(self) -> Result<f32, Self::Error> {
match self {
DataTypesCast::AnyCustomVariant(val) => Ok(val),
_ => {
Err({
let res = ::alloc::fmt::format(
format_args!(
"Cannot convert non-compatible DataTypesCast into {0}",
"f32"
),
);
res
})
}
}
}
}
// allows
impl std::convert::TryInto<[f32; 2]> for DataTypesCast {
type Error = String;
fn try_into(self) -> Result<[f32; 2], Self::Error> {
match self {
DataTypesCast::AnyCustomVariant2(val) => Ok(val),
_ => {
Err({
let res = ::alloc::fmt::format(
format_args!(
"Cannot convert non-compatible DataTypesCast into {0}",
"[f32 ; 2]"
),
);
res
})
}
}
}
}
} ``` If the parent enum has a large number of variants, this would be extremely tedious to type out.
Define your data structures and functions for parsing the binary data.
In the hashmapped_fields example, DataRecord
is a struct that contains a HashMap of field names and their parsed values. RecordDescs
is a struct that deserializes description information from the desc.xml
whose descriptions are then used to parse the data.dat
file into a DataRecord
. The example prints out the parsed data in the DataTypesCast
variants and then extracts and prints the values output from a match statement and output individually using try_into
.