1extern crate rand;
2extern crate serde;
3#[macro_use]
4extern crate failure;
5
6use std::result;
7
8use serde::de::{self, Visitor};
9
10pub type Error = failure::Error;
11
12pub trait Checkable: Sized {
13 const NAME: &'static str;
14 fn check(key: &str) -> Result<Self, failure::Error>;
15}
16
17#[derive(Debug, Fail)]
18pub enum ByteSequenceError {
19 #[fail(
20 display = "Failed to convert '{}' to '{}' because it had the wrong length",
21 raw_key,
22 typename
23 )]
24 InvalidKeyLen {
25 raw_key: String,
26 typename: &'static str,
27 },
28 #[fail(
29 display = "Failed to convert '{}' to '{}' because it contained invalid characters",
30 raw_key,
31 typename
32 )]
33 InvalidKeyChars {
34 raw_key: String,
35 typename: &'static str,
36 },
37}
38
39#[macro_export]
40macro_rules! byte_seq {
41 ($name:ident; $count:expr) => {
42 #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
43 pub struct $name([u8; $count]);
44
45 impl $crate::Checkable for $name {
46 const NAME: &'static str = stringify!($name);
47 fn check(key: &str) -> std::result::Result<$name, $crate::Error> {
48 ensure!(
49 key.chars().count() == ($count * 2),
50 $crate::ByteSequenceError::InvalidKeyLen {
51 raw_key: key.to_string(),
52 typename: stringify!($name)
53 }
54 );
55
56 let mut bytes: [u8; $count] = [0u8; $count];
57 for i in 0..$count {
58 if let Ok(byte) = u8::from_str_radix(&key[i * 2..i * 2 + 2], 16) {
59 bytes[i] = byte;
60 } else {
61 bail!($crate::ByteSequenceError::InvalidKeyChars {
62 raw_key: key.to_string(),
63 typename: stringify!($name)
64 })
65 }
66 }
67 Ok($name(bytes))
68 }
69 }
70
71 impl $name {
72 pub fn generate_new() -> $name {
73 use rand::thread_rng;
74 use rand::RngCore;
75
76 let mut bytes: [u8; $count] = [0u8; $count];
77 thread_rng().fill_bytes(&mut bytes);
78 $name(bytes)
79 }
80
81 pub fn to_string(&self) -> String {
82 let mut result = String::with_capacity($count * 2);
83 for byte in &self.0 {
84 result.push_str(&format!("{:0width$X}", byte, width = 2));
85 }
86 result
87 }
88 }
89
90 impl std::fmt::Display for $name {
91 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
92 write!(f, "{}({})", stringify!($name), self.to_string())
93 }
94 }
95
96 impl std::fmt::Debug for $name {
97 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
98 write!(f, "{}", self)
99 }
100 }
101
102 impl serde::Serialize for $name {
103 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
104 where
105 S: serde::Serializer,
106 {
107 serializer.serialize_str(&self.to_string())
108 }
109 }
110
111 impl<'de> serde::Deserialize<'de> for $name {
112 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
113 where
114 D: serde::Deserializer<'de>,
115 {
116 deserializer.deserialize_str($crate::CheckableVisitor {
117 _type: std::marker::PhantomData,
118 })
119 }
120 }
121 };
122}
123
124pub struct CheckableVisitor<T: Checkable> {
125 pub _type: std::marker::PhantomData<T>,
126}
127
128impl<'de, Checked: Checkable> Visitor<'de> for CheckableVisitor<Checked> {
129 type Value = Checked;
130
131 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
132 formatter.write_str("An $name in String format!")
133 }
134
135 fn visit_str<E>(self, value: &str) -> result::Result<Checked, E>
136 where
137 E: de::Error,
138 {
139 Checked::check(value)
140 .map_err(|e| E::custom(format!("Failed to deserialize {} '{:?}'", Checked::NAME, e)))
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use serde::{Deserialize, Deserializer, Serialize, Serializer};
148
149 byte_seq!(Test; 10);
150
151 #[test]
152 fn serialize_deserialize() {
153 let a = Test::generate_new();
154 assert_eq!(a, Test::check(&a.to_string()).unwrap());
155 }
156
157 byte_seq!(ApiKey; 32);
158
159 #[test]
160 fn example() {
161 let key = ApiKey::generate_new();
163
164 assert_eq!(key.to_string().len(), 64);
167
168 let key = ApiKey::check("BBC47F308F3D02C3C6C3D6C9555296A64407FE72AD92DE8C7344D610CFFABF67")
170 .unwrap();
171 assert_eq!(
172 key.to_string(),
173 "BBC47F308F3D02C3C6C3D6C9555296A64407FE72AD92DE8C7344D610CFFABF67"
174 );
175 }
176
177}