#![recursion_limit = "256"]
#![deny(dead_code, missing_docs, warnings)]
#[macro_use]
extern crate amplify;
use std::fmt::Debug;
use confined_encoding::{ConfinedDecode, ConfinedEncode, Error};
#[derive(Clone, PartialEq, Eq, Debug, Display, Error)]
pub enum EnumEncodingTestFailure<T>
where
T: Clone + PartialEq + Debug,
{
#[display("Failure during encoding enum variant `{0:02x?}`: {1:?}")]
EncoderFailure(T, String),
#[display(
"Failure during decoding binary representation of enum variant \
`{0:02x?}`: {1}
\tByte representation: {2:02x?}"
)]
DecoderFailure(T, String, Vec<u8>),
#[display(
"Roundtrip encoding of enum variant `{original:02x?}` results in \
different variant `{decoded:02x?}`"
)]
DecodedDiffersFromOriginal {
original: T,
decoded: T,
},
#[display(
"Expected value `{expected}` for enum variant \
`{enum_name}::{variant_name}` does not match the actual value \
`{actual}`"
)]
ValueMismatch {
enum_name: &'static str,
variant_name: &'static str,
expected: usize,
actual: usize,
},
#[display(
"Enum variant `{enum_name}:{variant_name}` has incorrect encoding:
\tExpected: {expected:02x?}
\tActual: {actual:02x?}
"
)]
EncodedValueMismatch {
enum_name: &'static str,
variant_name: &'static str,
expected: Vec<u8>,
actual: Vec<u8>,
},
#[display(
"Decoding of out-of-enum-range value `{0}` results in incorrect \
decoder error `{1:?}`"
)]
DecoderWrongErrorOnUnknownValue(
u8,
String,
),
#[display(
"Out-of-enum-range value `{0}` is interpreted as `{1:02x?}` enum \
variant by rust compiler"
)]
UnknownDecodesToVariant(
u8,
T,
),
#[display("Enum variant `{0:02x?}` is not equal to itself")]
FailedEq(#[doc = "Enum variant which is not equal to itself"] T),
#[display(
"Two distinct enum variants `{0:02x?}` and `{1:02x?}` are equal"
)]
FailedNe(
T,
T,
),
#[display(
"Comparing enum variants `{0:02x?}` and `{1:02x?}` results in wrong \
ordering"
)]
FailedOrd(
T,
T,
),
}
#[macro_export]
macro_rules! test_encoding_enum {
($enum:path as $ty:ty; $( $item:path => $val:expr ),+) => {
test_encoding_enum!(confined_encoding => $enum as $ty; $( $item => $val ),+)
};
($se:ident => $enum:path as $ty:ty; $( $item:path => $val:expr ),+) => {
Ok(())
$(
.and_then(|_| {
use $crate::EnumEncodingTestFailure;
match $se::confined_serialize(&$item) {
Ok(bytes) if bytes == &$val.to_le_bytes() => {
let deser = $se::confined_deserialize(bytes.clone())
.map_err(|e| EnumEncodingTestFailure::DecoderFailure(
$item, e.to_string(), bytes
))?;
if deser != $item {
Err(EnumEncodingTestFailure::DecodedDiffersFromOriginal {
original: $item,
decoded: deser,
})
} else {
Ok(())
}
},
Ok(wrong) => Err(EnumEncodingTestFailure::EncodedValueMismatch {
enum_name: stringify!($enum),
variant_name: stringify!($item),
expected: $val.to_le_bytes().to_vec(),
actual: wrong,
}),
Err(err) => Err(
EnumEncodingTestFailure::EncoderFailure($item, err.to_string())
),
}
})
)+
}
}
#[macro_export]
macro_rules! test_encoding_enum_by_values {
($enum:path as $ty:ty; $( $item:path => $val:expr ),+) => {
test_encoding_enum_by_values!(confined_encoding => $enum as $ty; $( $item => $val ),+)
};
($se:ident => $enum:path as $ty:ty; $( $item:path => $val:expr ),+) => {
test_encoding_enum!($se => $enum as $ty; $( $item => $val ),+)
$(
.and_then(|_| {
use $crate::EnumEncodingTestFailure;
if $item as $ty != ($val) {
return Err(EnumEncodingTestFailure::ValueMismatch {
enum_name: stringify!($enum),
variant_name: stringify!($item),
expected: ($val) as usize,
actual: $item as usize,
})
}
Ok(())
})
)+
.and_then(|_| {
use $crate::EnumEncodingTestFailure;
let mut all = ::std::collections::BTreeSet::new();
$( all.insert($item); )+
for (idx, a) in all.iter().enumerate() {
if a != a {
return Err(EnumEncodingTestFailure::FailedEq(*a));
}
for b in all.iter().skip(idx + 1) {
if a == b || (*a as usize) == (*b as usize) {
return Err(EnumEncodingTestFailure::FailedNe(*a, *b))
}
if (a >= b && (*a as usize) < (*b as usize)) ||
(a <= b && (*a as usize) > (*b as usize)) {
return Err(EnumEncodingTestFailure::FailedOrd(*a, *b))
}
}
}
Ok(())
})
}
}
#[macro_export]
macro_rules! test_encoding_enum_u8_exhaustive {
($enum:path; $( $item:path => $val:expr ),+) => {
test_encoding_enum_u8_exhaustive!(confined_encoding => $enum as u8; $( $item => $val ),+)
};
($enum:path as $ty:ty; $( $item:path => $val:expr ),+) => {
test_encoding_enum_u8_exhaustive!(confined_encoding => $enum as $ty; $( $item => $val ),+)
};
($se:ident => $enum:path; $( $item:path => $val:expr ),+) => {
test_encoding_enum_u8_exhaustive!($se => $enum as u8; $( $item => $val ),+)
};
($se:ident => $enum:path as $ty:ty; $( $item:path => $val:expr ),+) => {
test_encoding_enum_by_values!($se => $enum as $ty; $( $item => $val ),+).and_then(|_| {
use $crate::EnumEncodingTestFailure;
let mut set = ::std::collections::HashSet::new();
$( set.insert($val); )+
for x in 0..=u8::MAX {
if !set.contains(&x) {
match $se::confined_deserialize(&[x]) {
Err($se::Error::EnumValueNotKnown(stringify!($enum), a)) if a == x as usize => {},
Err(err) => return Err(
EnumEncodingTestFailure::DecoderWrongErrorOnUnknownValue(x, err.to_string())
),
Ok(variant) => return Err(
EnumEncodingTestFailure::UnknownDecodesToVariant(x, variant)
),
}
}
}
Ok(())
})
}
}
#[derive(Clone, PartialEq, Eq, Debug, Display, Error)]
pub enum DataEncodingTestFailure<T>
where
T: ConfinedEncode + ConfinedDecode + PartialEq + Debug + Clone,
{
#[display("Failure during encoding: {0:?}")]
EncoderFailure(#[doc = "Encoder error"] Error),
#[display(
"Failure during decoding: `{0:?}`
\tByte representation: {1:02x?}"
)]
DecoderFailure(
#[doc = "Decoder error"] Error,
#[doc = "Byte string which failed to decode"] Vec<u8>,
),
#[display(
"Encoder reported incorrect length of the serialized data: \
`{returned}` instead of `{actual}`"
)]
EncoderReturnedWrongLength {
actual: usize,
returned: usize,
},
#[display(
"Roundtrip encoding of `{original:x?}` produced different object \
`{transcoded:02x?}`"
)]
TranscodedObjectDiffersFromOriginal {
original: T,
transcoded: T,
},
#[display(
"Serialization of the object `{object:02x?}` decoded from a test \
vector results in a different byte string:
\tOriginal: {original:02x?}
\tSerialization: {transcoded:02x?}
"
)]
TranscodedVecDiffersFromOriginal {
original: Vec<u8>,
transcoded: Vec<u8>,
object: T,
},
}
#[inline]
pub fn test_object_encoding_roundtrip<T>(
object: &T,
) -> Result<Vec<u8>, DataEncodingTestFailure<T>>
where
T: ConfinedEncode + ConfinedDecode + PartialEq + Clone + Debug,
{
let mut encoded_object: Vec<u8> = vec![];
let written = object
.confined_encode(&mut encoded_object)
.map_err(DataEncodingTestFailure::EncoderFailure)?;
let len = encoded_object.len();
if written != len {
return Err(DataEncodingTestFailure::EncoderReturnedWrongLength {
actual: len,
returned: written,
});
}
let decoded_object =
T::confined_decode(&encoded_object[..]).map_err(|e| {
DataEncodingTestFailure::DecoderFailure(e, encoded_object.clone())
})?;
if &decoded_object != object {
return Err(
DataEncodingTestFailure::TranscodedObjectDiffersFromOriginal {
original: object.clone(),
transcoded: decoded_object,
},
);
}
Ok(encoded_object)
}
pub fn test_vec_decoding_roundtrip<T>(
test_vec: impl AsRef<[u8]>,
) -> Result<T, DataEncodingTestFailure<T>>
where
T: ConfinedEncode + ConfinedDecode + PartialEq + Clone + Debug,
{
let test_vec = test_vec.as_ref();
let decoded_object = T::confined_decode(test_vec).map_err(|e| {
DataEncodingTestFailure::DecoderFailure(e, test_vec.to_vec())
})?;
let encoded_object = test_object_encoding_roundtrip(&decoded_object)?;
if test_vec != encoded_object {
return Err(
DataEncodingTestFailure::TranscodedVecDiffersFromOriginal {
original: test_vec.to_vec(),
transcoded: encoded_object,
object: decoded_object,
},
);
}
Ok(decoded_object)
}
pub fn test_encoding_roundtrip<T>(
object: &T,
test_vec: impl AsRef<[u8]>,
) -> Result<(), DataEncodingTestFailure<T>>
where
T: ConfinedEncode + ConfinedDecode + PartialEq + Clone + Debug,
{
let decoded_object = test_vec_decoding_roundtrip(test_vec)?;
if object != &decoded_object {
return Err(
DataEncodingTestFailure::TranscodedObjectDiffersFromOriginal {
original: object.clone(),
transcoded: decoded_object,
},
);
}
Ok(())
}