binary_type_cast 0.1.0

A Rust crate for simplifying the process of parsing binary file data into various Rust data types using the TypeCast macro.
Documentation
# binary_type_cast
A Rust crate for simplifying the process of parsing binary file data into various Rust data types using the TypeCast macro.

## Under heavy development!
TODO: 
- Create Tests
- Better Error Handling

## Overview

 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.

## Usage
### Define a custom enum
In this example define `DataTypes`, and use the cast attribute for each variant. 

```rust
#[derive(Clone, Copy, Debug, Serialize, Deserialize, TypeCast)]
pub enum DataTypes {
    #[cast(from_le_bytes => f32)]
    AnyCustomVariant,
    #[cast(from_le_bytes => [f32;2])]
    AnyCustomVariant2

}
```

This will automatically generate a DataTypesCast enum:

```rust
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DataTypesCast {
    AnyCustomVariant(f32),
    AnyCustomVariant2([f32;2]),
}
```

---

#### Note:
Any Enum that utilizes the TypeCast will simply have `Cast` appended to the name. This:
```rust
#[derive(TypeCast)]
enum ExampleEnum {
    //
}
```
will generate the following at compile time:
```rust
#[derive(Clone, Debug, Serialize, Deserialize)]
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.

---

### Code Generation
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.split_at(std::mem::size_of::<f32>());
                    <f32>::from_le_bytes(bytes.try_into().unwrap())
                })
            }
            // This can be generated for any sized array supported by the standard library
            DataTypes::AnyCustomVariant2 => {
                DataTypesCast::AnyCustomVariant2({
                    let mut tmp_vec = std::vec::Vec::new();
                    for _ in 0..2 {
                        let (bytes, rest) = input
                            .split_at(std::mem::size_of::<f32>());
                        let converted = <f32>::from_le_bytes(
                            bytes.try_into().unwrap(),
                        );
                        *input = rest;
                        tmp_vec.push(converted);
                    }
                    let out: [f32; 2] = tmp_vec
                        .into_iter()
                        .collect::<Vec<f32>>()
                        .try_into()
                        .unwrap();
                    out
                })
            }
        }
    }

    // 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. 

## Next Steps
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`.