byte-sequence 0.1.0

A little marco that creates structs to be used as byte sequences (for ApiKeys, SessionIds and so on)
Documentation
extern crate serde;
extern crate rand;
#[macro_use]
extern crate error_chain;

mod errors;

use std::result;

use serde::de::{self, Visitor};

pub use errors::{Error, ErrorKind, Result};

pub trait Checkable: Sized {
    const NAME: &'static str;
    fn check(key: &str) -> Result<Self>;
}


#[macro_export]
macro_rules! byte_seq {
    ($name:ident; $count:expr) => {

        #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
        pub struct $name([u8; $count]);

        impl $crate::Checkable for $name {
            const NAME: &'static str = stringify!($name);
            fn check(key: &str) -> $crate::Result<$name> {
                ensure!(key.chars().count() == ($count * 2),
                       $crate::ErrorKind::InvalidKeyLen(key.to_string(), stringify!($name)));

                let mut bytes: [u8; $count] = [0u8; $count];
                for i in 0..$count {
                    if let Ok(byte) = u8::from_str_radix(&key[i*2..i*2+2], 16) {
                        bytes[i] = byte;
                    } else {
                        bail!($crate::ErrorKind::InvalidKeyChars(key.to_string(), stringify!($name)))
                    }
                }
                Ok($name(bytes))
            }

        }

        impl $name {

            pub fn generate_new() -> $name {
                use rand::thread_rng;
                use rand::Rng;

                let mut bytes: [u8; $count] = [0u8; $count];
                thread_rng().fill_bytes(&mut bytes);
                $name(bytes)
            }

            pub fn to_string(&self) -> String {
                let mut result = String::with_capacity($count * 2);
                for byte in &self.0 {
                    result.push_str(&format!("{:0width$X}", byte, width=2));
                }
                result
            }
        }

        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{}({})", stringify!($name), self.to_string())
            }
        }

        impl std::fmt::Debug for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{}", self)
            }
        }


        impl serde::Serialize for $name {
            fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
                where S: serde::Serializer
            {
                serializer.serialize_str(&self.to_string())
            }
        }


        impl<'de> serde::Deserialize<'de> for $name {
            fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
                where D: serde::Deserializer<'de>
            {
                deserializer.deserialize_str($crate::CheckableVisitor{_type:std::marker::PhantomData})
            }
        }

    }
}


pub struct CheckableVisitor<T: Checkable> {
    pub _type: std::marker::PhantomData<T>,
}

impl<'de, Checked: Checkable> Visitor<'de> for CheckableVisitor<Checked> {
    type Value = Checked;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("An $name in String format!")
    }

    fn visit_str<E>(self, value: &str) -> result::Result<Checked, E>
        where E: de::Error
    {
        Checked::check(value).map_err(|e| {
                                          E::custom(format!("Failed to deserialize {} '{:?}'",
                                                            Checked::NAME,
                                                            e))
                                      })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde::{Serialize, Serializer, Deserialize, Deserializer};

    byte_seq!(Test; 10);

    #[test]
    fn serialize_deserialize() {
        let a = Test::generate_new();
        assert_eq!(a, Test::check(&a.to_string()).unwrap());
    }

    byte_seq!(ApiKey; 32);

    #[test]
    fn example() {
        // Creates a new ApiKey containing 32 random bytes using a thread_rng
        let key = ApiKey::generate_new();

        // The to_string method creates a hex encoded string:
        // i.e. 'BBC47F308F3D02C3C6C3D6C9555296A64407FE72AD92DE8C7344D610CFFABF67'
        assert_eq!(key.to_string().len(), 64);

        // you can also do it the other way around: Parse a string into an ApiKey
        let key = ApiKey::check("BBC47F308F3D02C3C6C3D6C9555296A64407FE72AD92DE8C7344D610CFFABF67").unwrap();
        assert_eq!(key.to_string(), "BBC47F308F3D02C3C6C3D6C9555296A64407FE72AD92DE8C7344D610CFFABF67");
    }

}