use std::fmt::{Display, Formatter};
use std::io;
use std::num::{ParseFloatError, ParseIntError};
use std::str::Utf8Error;
use std::string::FromUtf8Error;
#[derive(Debug)]
pub enum Error {
DataError(DataError),
IoError(io::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Error::DataError(data_error) => data_error.fmt(f),
Error::IoError(io_error) => io_error.fmt(f),
}
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}
impl From<DataError> for Error {
fn from(value: DataError) -> Self {
Self::DataError(value)
}
}
impl From<FromUtf8Error> for Error {
fn from(value: FromUtf8Error) -> Self {
Self::from_utf8_error(value)
}
}
impl Error {
fn from_utf8_error(inner: FromUtf8Error) -> Self {
let bytes = inner.as_bytes();
let err = inner.utf8_error();
let (good_bytes, _) = bytes.split_at(err.valid_up_to());
let text: String = String::from_utf8_lossy(good_bytes).into_owned();
Self::DataError(DataError {
text,
line: None,
inner_error: err.into(),
})
}
pub fn unknown_key_error(key: String) -> Self {
Self::DataError(DataError {
text: key.to_owned(),
line: None,
inner_error: InnerError::UnknownKey,
})
}
}
#[derive(Debug, Clone)]
pub struct DataError {
text: String,
line: Option<usize>,
inner_error: InnerError,
}
impl DataError {
pub(crate) fn new_err<Err>(text: String, err: Err) -> Self
where
Err: Into<InnerError>,
{
DataError {
text,
line: None,
inner_error: err.into(),
}
}
pub(crate) fn new_data_width_error(text: String, expected: usize, actual: usize) -> Self {
Self::new_err(text, InnerError::InvalidWidth(expected, actual))
}
pub(crate) fn whitespace_error(text: String) -> Self {
Self::new_err(text, InnerError::WhitespaceError)
}
pub fn custom(parsed_value: &str, message: &str) -> Self {
DataError {
text: parsed_value.to_owned(),
inner_error: InnerError::Custom(message.to_owned()),
line: None,
}
}
pub(crate) fn with_line(&self, line: usize) -> Self {
let mut new_error = self.clone();
new_error.line = Some(line);
new_error
}
pub fn inner_error(&self) -> &InnerError {
&self.inner_error
}
}
impl Display for DataError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
fn fmt_err(text: &String, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Error handling data from \"{}\": ", text)
}
match &self.inner_error {
InnerError::Custom(s) => {
fmt_err(&self.text, f)?;
s.fmt(f)?;
}
InnerError::ParseIntError(e) => {
fmt_err(&self.text, f)?;
e.fmt(f)?;
}
InnerError::ParseFloatError(e) => {
fmt_err(&self.text, f)?;
e.fmt(f)?;
}
InnerError::Utf8Error(e) => {
fmt_err(&self.text, f)?;
e.fmt(f)?;
}
InnerError::UnknownKey => {
fmt_err(&self.text, f)?;
write!(f, "Unrecognized enum key")?;
}
InnerError::InvalidWidth(exp, act) => {
fmt_err(&self.text, f)?;
write!(
f,
"Expected field to have width {} but supplied value has width {}.",
exp, act
)?;
}
InnerError::WhitespaceError => {
fmt_err(&self.text, f)?;
write!(
f,
"Found non-whitespace character between data fields (strict)"
)?;
}
}
if let Some(line) = self.line {
write!(f, "\nError occured on line {}", line)?;
}
writeln!(f)
}
}
#[derive(Debug, Clone)]
pub enum InnerError {
Custom(String),
ParseIntError(ParseIntError),
ParseFloatError(ParseFloatError),
Utf8Error(Utf8Error),
UnknownKey,
InvalidWidth(usize, usize),
WhitespaceError,
}
impl From<ParseFloatError> for InnerError {
fn from(value: ParseFloatError) -> Self {
Self::ParseFloatError(value)
}
}
impl From<ParseIntError> for InnerError {
fn from(value: ParseIntError) -> Self {
Self::ParseIntError(value)
}
}
impl From<Utf8Error> for InnerError {
fn from(value: Utf8Error) -> Self {
Self::Utf8Error(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrap_io_error() {
fn need_error(_e: Error) -> bool {
true
}
let io_error: io::Error = io::Error::new(io::ErrorKind::AlreadyExists, "uh oh");
let b = need_error(io_error.into());
assert!(b);
}
#[test]
fn wrap_data_error() {
fn need_error(_e: Error) -> bool {
true
}
let data_error: DataError = DataError::new_err(
"can't parse this".to_string(),
InnerError::Custom("oh oh".to_string()),
);
let b = need_error(data_error.into());
assert!(b);
}
#[test]
fn wrap_utf8_error() {
fn need_error(_e: Error) -> bool {
true
}
let bytes: &[u8] = b"\x48\x69\xf0\x9f\x98\x89\xd1\x7b\x21";
let res = String::from_utf8(bytes.to_vec()).map_err(|err| err.into());
let b = need_error(res.unwrap_err());
assert!(b);
}
#[test]
fn utf8_error_text() {
let bytes: &[u8] = b"\x48\x69\xf0\x9f\x98\x89\xd1\x7b\x21";
let res: Error = String::from_utf8(bytes.to_vec())
.map_err(|err| err.into())
.unwrap_err();
match res {
Error::DataError(de) => {
assert_eq!(de.text, String::from("Hi😉"));
match de.inner_error {
InnerError::Utf8Error(inner) => {
assert_eq!(inner.valid_up_to(), 6);
}
_ => assert!(false),
}
}
Error::IoError(_) => assert!(false),
}
}
#[test]
fn map_io_error() {
use std::io::Write;
fn try_write<W: Write>(buf: &mut W, bytes: &[u8]) -> Result<usize, Error> {
let mut bytes_written = 0;
bytes_written += buf.write(bytes)?;
Ok(bytes_written)
}
let mut buf: Vec<u8> = Vec::new();
let word: String = String::from("Hello!");
let n_bytes = try_write(&mut buf, word.as_bytes()).unwrap();
assert_eq!(n_bytes, 6);
struct FailedWritter {}
impl Write for FailedWritter {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
Err(io::Error::new(io::ErrorKind::InvalidData, "failed"))
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
let mut buf = FailedWritter {};
let word: String = String::from("1234567!");
let res: Result<usize, Error> = try_write(&mut buf, word.as_bytes());
match res {
Err(Error::IoError(err)) => {
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
}
Err(Error::DataError(_)) => panic!("Expected IO Error"),
Ok(_) => panic!("Expected IO Error"),
};
}
}