use log;
use quick_xml;
use std::string::FromUtf8Error;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RssError {
#[error("XML error occurred: {0}")]
XmlWriteError(#[from] quick_xml::Error),
#[error("XML parse error occurred: {0}")]
XmlParseError(quick_xml::Error),
#[error("UTF-8 conversion error occurred: {0}")]
Utf8Error(#[from] FromUtf8Error),
#[error("A required field is missing: {0}")]
MissingField(String),
#[error("Date parse error: {0}")]
DateParseError(String),
#[error("I/O error occurred: {0}")]
IoError(#[from] std::io::Error),
#[error("Invalid input data provided: {0}")]
InvalidInput(String),
#[error("Invalid URL provided: {0}")]
InvalidUrl(String),
#[error("Unknown XML element found: {0}")]
UnknownElement(String),
#[error("Validation errors: {0:?}")]
ValidationErrors(Vec<String>),
#[error("Date sort error: {0:?}")]
DateSortError(Vec<DateSortError>),
#[error("Item validation error: {0}")]
ItemValidationError(String),
#[error("Unknown field encountered: {0}")]
UnknownField(String),
#[error("Custom error: {0}")]
Custom(String),
#[error("Invalid RSS version: {0}")]
InvalidRssVersion(String),
}
#[derive(Debug, Error)]
#[non_exhaustive]
#[error("Validation error: {message}")]
pub struct ValidationError {
pub field: String,
pub message: String,
}
#[derive(Debug, Error)]
#[non_exhaustive]
#[error("Date sort error: {message}")]
pub struct DateSortError {
pub index: usize,
pub message: String,
}
pub type Result<T> = std::result::Result<T, RssError>;
impl RssError {
pub fn missing_field<S: Into<String>>(field_name: S) -> Self {
RssError::MissingField(field_name.into())
}
pub fn date_sort_error<S: Into<String>>(
index: usize,
message: S,
) -> DateSortError {
DateSortError {
index,
message: message.into(),
}
}
pub fn invalid_input<S: Into<String>>(message: S) -> Self {
RssError::InvalidInput(message.into())
}
pub fn custom<S: Into<String>>(message: S) -> Self {
RssError::Custom(message.into())
}
pub fn log(&self) {
log::error!("RSS Error occurred: {}", self);
}
#[must_use]
pub fn to_http_status(&self) -> u16 {
match self {
RssError::XmlWriteError(_)
| RssError::XmlParseError(_)
| RssError::Utf8Error(_)
| RssError::IoError(_)
| RssError::UnknownElement(_)
| RssError::DateSortError(_)
| RssError::UnknownField(_)
| RssError::Custom(_) => 500,
RssError::MissingField(_)
| RssError::InvalidInput(_)
| RssError::DateParseError(_)
| RssError::InvalidUrl(_)
| RssError::ValidationErrors(_)
| RssError::ItemValidationError(_)
| RssError::InvalidRssVersion(_) => 400,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
use std::io;
#[test]
fn test_rss_error_display() {
let error = RssError::missing_field("title");
assert_eq!(
error.to_string(),
"A required field is missing: title"
);
}
#[test]
fn test_xml_write_error() {
let xml_error = quick_xml::Error::Io(std::sync::Arc::new(
io::Error::new(io::ErrorKind::Other, "XML error"),
));
let error = RssError::XmlWriteError(xml_error);
assert_eq!(
error.to_string(),
"XML error occurred: I/O error: XML error"
);
}
#[test]
fn test_utf8_error() {
let utf8_error =
String::from_utf8(vec![0, 159, 146, 150]).unwrap_err();
let error = RssError::Utf8Error(utf8_error);
assert_eq!(error.to_string(), "UTF-8 conversion error occurred: invalid utf-8 sequence of 1 bytes from index 1");
}
#[test]
fn test_io_error() {
let io_error =
io::Error::new(io::ErrorKind::NotFound, "File not found");
let error: RssError = io_error.into();
assert_eq!(
error.to_string(),
"I/O error occurred: File not found"
);
}
#[test]
fn test_error_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<RssError>();
}
#[test]
fn test_error_source() {
let xml_error = quick_xml::Error::Io(std::sync::Arc::new(
io::Error::new(io::ErrorKind::NotFound, "File not found"),
));
let error = RssError::XmlWriteError(xml_error);
assert!(error.source().is_some());
let io_error: RssError =
io::Error::new(io::ErrorKind::NotFound, "File not found")
.into();
assert!(io_error.source().is_some());
}
#[test]
fn test_missing_field_with_string() {
let error = RssError::missing_field(String::from("author"));
assert_eq!(
error.to_string(),
"A required field is missing: author"
);
}
#[test]
fn test_missing_field_with_str() {
let error = RssError::missing_field("description");
assert_eq!(
error.to_string(),
"A required field is missing: description"
);
}
#[test]
fn test_error_downcast() {
let error: Box<dyn Error> =
Box::new(RssError::missing_field("category"));
let downcast_result = error.downcast::<RssError>();
assert!(downcast_result.is_ok());
}
#[test]
fn test_invalid_input_error() {
let error = RssError::invalid_input("Invalid date format");
assert_eq!(
error.to_string(),
"Invalid input data provided: Invalid date format"
);
}
#[test]
fn test_custom_error() {
let error = RssError::custom("Unforeseen error occurred");
assert_eq!(
error.to_string(),
"Custom error: Unforeseen error occurred"
);
}
#[test]
fn test_to_http_status() {
assert_eq!(
RssError::missing_field("title").to_http_status(),
400
);
assert_eq!(
RssError::XmlWriteError(quick_xml::Error::Io(
std::sync::Arc::new(io::Error::new(
io::ErrorKind::Other,
"XML error"
))
))
.to_http_status(),
500
);
assert_eq!(
RssError::InvalidInput("Bad input".to_string())
.to_http_status(),
400
);
}
#[test]
fn test_validation_error() {
let error = ValidationError {
field: "some_field".to_string(),
message: "Invalid field".to_string(),
};
assert_eq!(
error.to_string(),
"Validation error: Invalid field"
);
}
#[test]
fn test_date_sort_error() {
let error = DateSortError {
index: 0,
message: "Invalid date".to_string(),
};
assert_eq!(error.to_string(), "Date sort error: Invalid date");
}
#[test]
fn test_missing_field_error() {
let rss_error = RssError::MissingField("title".to_string());
assert_eq!(
format!("{}", rss_error),
"A required field is missing: title"
);
}
#[test]
fn test_date_parse_error() {
let rss_error =
RssError::DateParseError("Invalid date format".to_string());
assert_eq!(
format!("{}", rss_error),
"Date parse error: Invalid date format"
);
}
#[test]
fn test_invalid_url_error() {
let rss_error =
RssError::InvalidUrl("https://invalid-url".to_string());
assert_eq!(
format!("{}", rss_error),
"Invalid URL provided: https://invalid-url"
);
}
#[test]
fn test_unknown_element_error() {
let rss_error =
RssError::UnknownElement("unknown-element".to_string());
assert_eq!(
format!("{}", rss_error),
"Unknown XML element found: unknown-element"
);
}
#[test]
fn test_validation_errors() {
let validation_errors = vec![
"Title is missing".to_string(),
"Invalid pub date".to_string(),
];
let rss_error =
RssError::ValidationErrors(validation_errors.clone());
assert_eq!(
format!("{}", rss_error),
format!("Validation errors: {:?}", validation_errors)
);
}
}