use crate::error::{DataError, Error, InnerError};
use crate::format::{Alignment, FieldDescription};
use crate::ReadFixed;
pub trait FixedDeserializer {
fn parse_fixed(s: &str, desc: &FieldDescription) -> Result<Self, DataError>
where
Self: Sized;
}
fn extract_trimmed<'a>(src: &'a str, desc: &FieldDescription) -> Result<&'a str, DataError> {
if desc.strict && !&src[..desc.skip].trim().is_empty() {
return Err(DataError::whitespace_error(String::from(src)));
}
let end = std::cmp::min(desc.skip + desc.len, src.len());
let slice = &src[desc.skip..end];
let res = match (desc.strict, desc.alignment) {
(true, Alignment::Left) => slice.trim_end(),
(true, Alignment::Right) => slice.trim_start(),
(true, Alignment::Full) => slice,
_ => slice.trim_start().trim_end(),
};
Ok(res)
}
macro_rules! fixed_deserializer_float_impl {
($t:ty) => {
impl FixedDeserializer for $t {
fn parse_fixed(s: &str, desc: &FieldDescription) -> Result<$t, DataError> {
let trimmed = extract_trimmed(s, desc)?;
trimmed.parse::<$t>().map_err(|e| {
DataError::new_err(trimmed.to_string(), InnerError::ParseFloatError(e))
})
}
}
};
}
fixed_deserializer_float_impl!(f32);
fixed_deserializer_float_impl!(f64);
macro_rules! fixed_deserializer_int_impl {
($t:ty) => {
impl FixedDeserializer for $t {
fn parse_fixed(s: &str, desc: &FieldDescription) -> Result<$t, DataError> {
let trimmed = extract_trimmed(s, desc)?;
if desc.strict && desc.alignment == Alignment::Full && trimmed.len() != s.len() {
let trimmed_len = trimmed.len();
Err(DataError::new_data_width_error(
String::from(trimmed),
trimmed_len,
s.len(),
))
} else {
trimmed.parse::<$t>().map_err(|e| {
DataError::new_err(trimmed.to_string(), InnerError::ParseIntError(e))
})
}
}
}
};
}
fixed_deserializer_int_impl!(u8);
fixed_deserializer_int_impl!(u16);
fixed_deserializer_int_impl!(u32);
fixed_deserializer_int_impl!(u64);
fixed_deserializer_int_impl!(u128);
fixed_deserializer_int_impl!(i8);
fixed_deserializer_int_impl!(i16);
fixed_deserializer_int_impl!(i32);
fixed_deserializer_int_impl!(i64);
fixed_deserializer_int_impl!(i128);
fixed_deserializer_int_impl!(usize);
fixed_deserializer_int_impl!(isize);
impl FixedDeserializer for String {
fn parse_fixed(s: &str, desc: &FieldDescription) -> Result<String, DataError> {
let slice = &s[desc.skip..desc.skip + desc.len];
let trimmed = match desc.alignment {
Alignment::Left => slice.trim_end(),
Alignment::Right => slice.trim_start(),
Alignment::Full => slice,
};
Ok(trimmed.to_string())
}
}
impl<T: ReadFixed> FixedDeserializer for T {
fn parse_fixed(s: &str, desc: &FieldDescription) -> Result<Self, DataError> {
let slice = &s[desc.skip..desc.skip + desc.len];
let obj = T::read_fixed_str(slice).map_err(|e| match e {
Error::DataError(e) => e,
Error::IoError(e) => {
panic!("I/O error while reading internal memory: {:?}", e);
}
})?;
Ok(obj)
}
}
impl<T: FixedDeserializer> FixedDeserializer for Option<T> {
fn parse_fixed(s: &str, desc: &FieldDescription) -> Result<Self, DataError> {
let slice = &s[desc.skip..desc.skip + desc.len];
if slice.trim_start().is_empty() {
Ok(None)
} else {
Ok(Some(T::parse_fixed(s, desc)?))
}
}
}
#[cfg(test)]
mod tests {
use std::str::from_utf8;
use super::*;
#[test]
fn extract_string_left() {
let desc = FieldDescription {
skip: 0,
len: 3,
alignment: Alignment::Left,
strict: false,
};
let actual: String = String::parse_fixed("abc ", &desc).unwrap();
let expected = "abc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_left_pad() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Left,
strict: false,
};
let actual: String = String::parse_fixed("abc ", &desc).unwrap();
let expected = "abc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_left_skip() {
let desc = FieldDescription {
skip: 1,
len: 5,
alignment: Alignment::Left,
strict: false,
};
let actual: String = String::parse_fixed("abc ", &desc).unwrap();
let expected = "bc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_left_truncate() {
let desc = FieldDescription {
skip: 0,
len: 2,
alignment: Alignment::Left,
strict: false,
};
let actual: String = String::parse_fixed("abc ", &desc).unwrap();
let expected = "ab".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_left_ws() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Left,
strict: false,
};
let actual: String = String::parse_fixed("a bc ", &desc).unwrap();
let expected = "a bc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_left_leading_ws() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Left,
strict: false,
};
let actual: String = String::parse_fixed(" abc ", &desc).unwrap();
let expected = " abc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_right_exact() {
let desc = FieldDescription {
skip: 0,
len: 3,
alignment: Alignment::Right,
strict: false,
};
let actual: String = String::parse_fixed(" abc", &desc).unwrap();
let expected = "".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_right() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Right,
strict: false,
};
let actual: String = String::parse_fixed(" abc", &desc).unwrap();
let expected = "abc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_right_skip() {
let desc = FieldDescription {
skip: 1,
len: 5,
alignment: Alignment::Right,
strict: false,
};
let actual: String = String::parse_fixed(" abc", &desc).unwrap();
let expected = "abc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_right_skip_into() {
let desc = FieldDescription {
skip: 4,
len: 2,
alignment: Alignment::Right,
strict: false,
};
let actual: String = String::parse_fixed(" abc", &desc).unwrap();
let expected = "bc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_right_truncate() {
let desc = FieldDescription {
skip: 1,
len: 4,
alignment: Alignment::Right,
strict: false,
};
let actual: String = String::parse_fixed(" abc", &desc).unwrap();
let expected = "ab".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_right_ws() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Right,
strict: false,
};
let actual: String = String::parse_fixed(" a bc", &desc).unwrap();
let expected = "a bc".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_right_trailing_ws() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Right,
strict: false,
};
let actual: String = String::parse_fixed(" abc ", &desc).unwrap();
let expected = "abc ".to_string();
assert_eq!(actual, expected)
}
#[test]
fn extract_string_full() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed("abcdef", &desc).unwrap();
let expected = "abcdef".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_slice() {
let desc = FieldDescription {
skip: 1,
len: 3,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed("abcdef", &desc).unwrap();
let expected = "bcd".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_left() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed("abc ", &desc).unwrap();
let expected = "abc ".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_right() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed(" abc", &desc).unwrap();
let expected = " abc".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_skip() {
let desc = FieldDescription {
skip: 1,
len: 5,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed("abc ", &desc).unwrap();
let expected = "bc ".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_truncate() {
let desc = FieldDescription {
skip: 0,
len: 4,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed("abc ", &desc).unwrap();
let expected = "abc ".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_ws() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed(" a bc ", &desc).unwrap();
let expected = " a bc ".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_trimmed_ws() {
let desc = FieldDescription {
skip: 1,
len: 3,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed(" ab c ", &desc).unwrap();
let expected = "ab ".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_string_full_tight_wc() {
let desc = FieldDescription {
skip: 1,
len: 4,
alignment: Alignment::Full,
strict: false,
};
let actual: String = String::parse_fixed(" ab c ", &desc).unwrap();
let expected = "ab c".to_string();
assert_eq!(actual, expected);
}
#[test]
fn extract_f32_padding() {
let descs = vec![
FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Full,
strict: false,
},
FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Left,
strict: false,
},
FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Right,
strict: false,
},
];
let expected: f32 = 3.14;
let mut tests_run = 0;
for desc in descs {
let actual: f32 = f32::parse_fixed(" 3.14 ", &desc).unwrap();
assert_eq!(actual, expected);
let actual: f32 = f32::parse_fixed("3.14 ", &desc).unwrap();
assert_eq!(actual, expected);
let actual: f32 = f32::parse_fixed(" 3.14", &desc).unwrap();
assert_eq!(actual, expected);
tests_run += 1;
}
assert_eq!(tests_run, 3);
}
#[test]
fn extract_f32_full() {
let desc = FieldDescription {
skip: 1,
len: 4,
alignment: Alignment::Full,
strict: true,
};
let actual: f32 = f32::parse_fixed(" 3.14 ", &desc).unwrap();
let expected: f32 = 3.14;
assert_eq!(actual, expected);
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Full,
strict: true,
};
let actual: Result<f32, DataError> = f32::parse_fixed(" 3.14 ", &desc);
match actual {
Ok(_) => panic!("Expected parse_fixed call to fail"),
Err(e) => match e.inner_error() {
InnerError::ParseFloatError(_) => {}
_ => panic!("Expected ParseFloatError as inner error"),
},
}
}
#[test]
fn extract_f32_left() {
let desc = FieldDescription {
skip: 1,
len: 5,
alignment: Alignment::Left,
strict: true,
};
let actual: f32 = f32::parse_fixed(" 3.14 ", &desc).unwrap();
let expected: f32 = 3.14;
assert_eq!(actual, expected);
let desc = FieldDescription {
skip: 2,
len: 4,
alignment: Alignment::Left,
strict: false,
};
let actual: f32 = f32::parse_fixed(" 3.14 ", &desc).unwrap();
let expected: f32 = 0.14;
assert_eq!(actual, expected);
}
#[test]
fn extract_f32_right() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: true,
};
let actual: f32 = f32::parse_fixed(" 3.14 ", &desc).unwrap();
let expected: f32 = 3.14;
assert_eq!(actual, expected);
}
#[test]
fn extract_f32_right_strict() {
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Right,
strict: false,
};
let actual: f32 = f32::parse_fixed(" 3.14 ", &desc).unwrap();
let expected: f32 = 3.14;
assert_eq!(actual, expected);
let desc = FieldDescription {
skip: 0,
len: 6,
alignment: Alignment::Right,
strict: true,
};
match f32::parse_fixed(" 3.14 ", &desc) {
Ok(_) => panic!("Expected parse_fixed call to fail"),
Err(e) => match e.inner_error() {
InnerError::ParseFloatError(_) => {}
_ => panic!("Expected ParseFloatError as inner error"),
},
}
}
#[test]
fn extract_f32_bad() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: false,
};
let actual: Result<f32, DataError> = f32::parse_fixed(" 3a14 ", &desc);
let expected = "Error handling data from \"3a14\": invalid float literal\n";
assert_eq!(actual.unwrap_err().to_string(), expected);
}
#[test]
fn strict_numeric_zero_padding() {
let desc = FieldDescription {
skip: 0,
len: 3,
alignment: Alignment::Full,
strict: false,
};
let actual = u8::parse_fixed("042", &desc).unwrap();
assert_eq!(actual, 42);
let desc = FieldDescription {
skip: 0,
len: 3,
alignment: Alignment::Full,
strict: true,
};
let actual = u8::parse_fixed("042", &desc).unwrap();
assert_eq!(actual, 42);
let desc = FieldDescription {
skip: 0,
len: 3,
alignment: Alignment::Full,
strict: false,
};
let actual = u8::parse_fixed(" 42", &desc).unwrap();
assert_eq!(actual, 42);
let desc = FieldDescription {
skip: 0,
len: 3,
alignment: Alignment::Full,
strict: true,
};
let actual = u8::parse_fixed(" 42", &desc);
assert!(actual.is_err());
assert_eq!(
actual.unwrap_err().to_string(),
"Error handling data from \" 42\": invalid digit found in string\n"
);
}
#[test]
fn strict_left_align() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Left,
strict: false,
};
let actual = u8::parse_fixed(" 42 ", &desc).unwrap();
assert_eq!(actual, 42);
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Left,
strict: true,
};
let actual = u8::parse_fixed(" 42 ", &desc);
assert!(actual.is_err());
assert_eq!(
actual.unwrap_err().to_string(),
"Error handling data from \" 42\": invalid digit found in string\n"
);
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Left,
strict: true,
};
let actual = u8::parse_fixed("42 ", &desc).unwrap();
assert_eq!(actual, 42);
}
#[test]
fn strict_right_align() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: false,
};
let actual = u8::parse_fixed(" 42 ", &desc).unwrap();
assert_eq!(actual, 42);
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: true,
};
let actual = u8::parse_fixed(" 42 ", &desc);
assert!(actual.is_err());
assert_eq!(
actual.unwrap_err().to_string(),
"Error handling data from \"42 \": invalid digit found in string\n"
);
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: true,
};
let actual = u8::parse_fixed(" 42", &desc).unwrap();
assert_eq!(actual, 42);
}
#[test]
fn impl_parse() {
#[derive(PartialEq, Eq, Debug)]
enum Thing {
Thing1,
Thing2,
}
impl ReadFixed for Thing {
fn read_fixed<R>(buf: &mut R) -> Result<Self, Error>
where
Self: Sized,
R: std::io::Read,
{
let mut v: [u8; 2] = [0; 2];
let res = buf.read_exact(&mut v);
assert!(res.is_ok());
let s = from_utf8(v.as_slice()).unwrap();
match s {
"T1" => Ok(Self::Thing1),
"T2" => Ok(Self::Thing2),
x => Err(DataError::custom(x, "failed").into()),
}
}
}
let thing = Thing::parse_fixed(
" T1 ",
&FieldDescription {
skip: 1,
len: 2,
alignment: Alignment::Left,
strict: true,
},
);
assert_eq!(thing.unwrap(), Thing::Thing1);
}
#[test]
fn parse_option_some() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: true,
};
let actual = Option::<u16>::parse_fixed(" 42", &desc).unwrap();
assert_eq!(actual, Some(42));
}
#[test]
fn parse_option_none() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: true,
};
let actual = Option::<u16>::parse_fixed(" ", &desc).unwrap();
assert_eq!(actual, None);
}
}