use thiserror::Error;
use crate::DataType;
#[derive(Error, Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub enum InputError {
#[error("invalid element width {0}")]
InvalidWidth(usize),
#[error("{len} bytes are not a multiple of the {width}-byte element width")]
NotMultiple {
len: usize,
width: usize,
},
#[error("the string lengths sum to {sum} but the content is {len} bytes")]
LengthMismatch {
sum: u64,
len: usize,
},
}
fn check_multiple(bytes: &[u8], width: usize) -> Result<(), InputError> {
if !bytes.len().is_multiple_of(width) {
return Err(InputError::NotMultiple {
len: bytes.len(),
width,
});
}
Ok(())
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Numbers<'a> {
bytes: &'a [u8],
width: usize,
}
impl<'a> TryFrom<(&'a [u8], usize)> for Numbers<'a> {
type Error = InputError;
fn try_from((bytes, width): (&'a [u8], usize)) -> Result<Self, Self::Error> {
if !matches!(width, 1 | 2 | 4 | 8) {
return Err(InputError::InvalidWidth(width));
}
check_multiple(bytes, width)?;
Ok(Self { bytes, width })
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Records<'a> {
bytes: &'a [u8],
width: usize,
}
impl<'a> TryFrom<(&'a [u8], usize)> for Records<'a> {
type Error = InputError;
fn try_from((bytes, width): (&'a [u8], usize)) -> Result<Self, Self::Error> {
if width == 0 {
return Err(InputError::InvalidWidth(width));
}
check_multiple(bytes, width)?;
Ok(Self { bytes, width })
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Strings<'a> {
bytes: &'a [u8],
lens: &'a [u32],
}
impl<'a> TryFrom<(&'a [u8], &'a [u32])> for Strings<'a> {
type Error = InputError;
fn try_from((bytes, lens): (&'a [u8], &'a [u32])) -> Result<Self, Self::Error> {
let sum: u64 = lens.iter().map(|&len| u64::from(len)).sum();
if sum != bytes.len() as u64 {
return Err(InputError::LengthMismatch {
sum,
len: bytes.len(),
});
}
Ok(Self { bytes, lens })
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Repr<'a> {
Serial(&'a [u8]),
Records(Records<'a>),
Numbers(Numbers<'a>),
Strings(Strings<'a>),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Input<'a> {
repr: Repr<'a>,
metadata: &'a [(i32, i32)],
}
impl<'a> Input<'a> {
const fn new(repr: Repr<'a>) -> Self {
Self {
repr,
metadata: &[],
}
}
#[must_use]
pub const fn with_metadata(mut self, metadata: &'a [(i32, i32)]) -> Self {
self.metadata = metadata;
self
}
}
impl<'a> From<&'a [u8]> for Input<'a> {
fn from(bytes: &'a [u8]) -> Self {
Self::new(Repr::Serial(bytes))
}
}
impl<'a> From<Numbers<'a>> for Input<'a> {
fn from(numbers: Numbers<'a>) -> Self {
Self::new(Repr::Numbers(numbers))
}
}
impl<'a> From<Records<'a>> for Input<'a> {
fn from(records: Records<'a>) -> Self {
Self::new(Repr::Records(records))
}
}
impl<'a> From<Strings<'a>> for Input<'a> {
fn from(strings: Strings<'a>) -> Self {
Self::new(Repr::Strings(strings))
}
}
impl<'a> Input<'a> {
#[must_use]
pub const fn data_type(&self) -> DataType {
match self.repr {
Repr::Serial(_) => DataType::Serial,
Repr::Records(_) => DataType::Struct,
Repr::Numbers(_) => DataType::Numeric,
Repr::Strings(_) => DataType::String,
}
}
#[must_use]
pub const fn len(&self) -> usize {
match self.repr {
Repr::Serial(bytes) => bytes.len(),
Repr::Records(Records { bytes, width }) | Repr::Numbers(Numbers { bytes, width }) => {
bytes.len() / width
}
Repr::Strings(strings) => strings.lens.len(),
}
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[cfg(feature = "compression")]
pub(crate) fn to_stream(self) -> crate::compressor::core::data::Stream<'a> {
use crate::compressor::core::data::{Bytes, Stream};
let mut stream = match self.repr {
Repr::Serial(bytes) => Stream::serial(bytes),
Repr::Records(Records { bytes, width }) => {
Stream::new(DataType::Struct, width, Bytes::Borrowed(bytes))
}
Repr::Numbers(Numbers { bytes, width }) => {
Stream::new(DataType::Numeric, width, Bytes::Borrowed(bytes))
}
Repr::Strings(Strings { bytes, lens }) => Stream::strings(bytes, lens),
};
for &(id, value) in self.metadata {
stream.set_metadata(id, value);
}
stream
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constructors_validate_widths_and_lengths() {
assert_eq!(
Numbers::try_from((&[0u8; 6][..], 3)),
Err(InputError::InvalidWidth(3))
);
assert_eq!(
Numbers::try_from((&[0u8; 6][..], 4)),
Err(InputError::NotMultiple { len: 6, width: 4 })
);
assert_eq!(
Records::try_from((&[0u8; 6][..], 0)),
Err(InputError::InvalidWidth(0))
);
assert!(Records::try_from((&[0u8; 6][..], 3)).is_ok());
assert_eq!(
Strings::try_from((&b"abc"[..], &[1u32, 1][..])),
Err(InputError::LengthMismatch { sum: 2, len: 3 })
);
assert!(Strings::try_from((&b""[..], &[][..])).is_ok());
}
#[test]
fn inputs_report_type_and_length() {
let records = Records::try_from((&[0u8; 12][..], 3)).unwrap();
let strings = Strings::try_from((&b"abc"[..], &[3u32][..])).unwrap();
let inputs = [
Input::from(&b"xy"[..]),
Input::from(records),
Input::from(Numbers::try_from((&[0u8; 8][..], 2)).unwrap()),
Input::from(strings),
];
let types: alloc::vec::Vec<_> = inputs.iter().map(Input::data_type).collect();
assert_eq!(
types,
[
DataType::Serial,
DataType::Struct,
DataType::Numeric,
DataType::String
]
);
let lens: alloc::vec::Vec<_> = inputs.iter().map(Input::len).collect();
assert_eq!(lens, [2, 4, 4, 1]);
assert!(Input::from(&b""[..]).is_empty());
assert!(!inputs[0].is_empty());
assert!(alloc::format!("{}", InputError::InvalidWidth(3)).contains('3'));
let streams: alloc::vec::Vec<_> = inputs.iter().map(|i| i.to_stream()).collect();
assert_eq!(streams[1].width(), 3);
assert_eq!(streams[3].lens(), [3]);
}
}