use std::fmt::Display;
use thiserror::Error;
use winnow::error::{ContextError, ParseError};
#[derive(Error, Debug)]
pub enum SphinxInvError {
#[error("IO error")]
IoError(#[from] std::io::Error),
#[error("Parse error: {0}")]
ParseError(#[from] SphinxParseError),
#[error("Input was missing the following header component: {0}")]
IncompleteHeader(MissingHeaderComponent),
#[error("Unsupported inventory version: {0}")]
UnsupportedInventoryVersion(u8),
#[error("Unsupported compression method: {0}")]
UnsupportedCompressionMethod(String),
}
#[derive(Error, Debug, PartialEq)]
pub enum MissingHeaderComponent {
#[error("inventory format version")]
InvVersion,
#[error("project name")]
ProjectName,
#[error("project version")]
ProjectVersion,
#[error("compression description")]
CompressionDescription,
}
#[derive(Error, Debug, PartialEq)]
pub struct SphinxParseError {
pub input: String,
pub message: String,
pub location: usize,
pub line_num: usize,
}
impl SphinxParseError {
pub fn from_str(line: &str, message: &str, location: usize, line_num: usize) -> Self {
Self {
input: line.to_string(),
message: message.to_string(),
location,
line_num,
}
}
}
impl Display for SphinxParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("Parsing error on line {0}: \n", self.line_num))?;
f.write_str(&format!("{}\n", &self.input))?;
f.write_str(&format!("{}^\n", " ".repeat(self.location)))?;
f.write_str(&self.message)
}
}
impl SphinxParseError {
pub fn from_byte_parse(error: &ParseError<&[u8], ContextError>, line_num: usize) -> Self {
let message = error.inner().to_string();
let byte_buf = (*error.input()).to_owned();
let line = match String::from_utf8(byte_buf.clone()) {
Ok(s) => s,
Err(_) => format!("{byte_buf:?}"),
};
let location = error.offset();
Self {
input: line,
message,
location,
line_num,
}
}
pub fn from_str_parse(error: &ParseError<&str, ContextError>, line_num: usize) -> Self {
let message = error.inner().to_string();
let line = *error.input();
let location = error.offset();
Self {
input: line.to_string(),
message,
location,
line_num,
}
}
}
#[cfg(test)]
impl PartialEq for SphinxInvError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::IoError(_), Self::IoError(_)) => true,
(Self::ParseError(l0), Self::ParseError(r0)) => l0 == r0,
(Self::UnsupportedInventoryVersion(l0), Self::UnsupportedInventoryVersion(r0)) => {
l0 == r0
}
(Self::UnsupportedCompressionMethod(l0), Self::UnsupportedCompressionMethod(r0)) => {
l0 == r0
}
(Self::IncompleteHeader(l0), Self::IncompleteHeader(r0)) => l0 == r0,
_ => false,
}
}
}
#[cfg(test)]
mod test {
use crate::error::SphinxParseError;
#[test]
fn parse_error_render() {
let err =
SphinxParseError::from_str("foo bar baz", "baz is deprecated, please use soup", 10, 0);
assert_eq!(
err.to_string(),
"Parsing error on line 0: \nfoo bar baz\n ^\nbaz is deprecated, please use soup"
);
}
}