use crate::{
cache::CacheError, document_parser::DocumentParseError, http_client::HttpClientError,
sitemap::SitemapError,
};
use core::{
error,
fmt::{self, Display, Formatter},
str::Utf8Error,
};
use data_url::{DataUrlError, forgiving_base64::InvalidBase64};
use http::StatusCode;
use muffy_validation::MarkupError;
use serde::{Serialize, Serializer};
use std::io;
use tokio::{sync::AcquireError, task::JoinError};
use url::ParseError;
#[derive(Debug)]
pub enum Error {
Acquire(AcquireError),
Cache(CacheError),
DocumentParse(DocumentParseError),
Io(io::Error),
Item(ItemError),
Join(JoinError),
Json(serde_json::Error),
UrlParse(ParseError),
Utf8(Utf8Error),
Validation,
}
impl error::Error for Error {}
impl Display for Error {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Acquire(error) => write!(formatter, "{error}"),
Self::Cache(error) => write!(formatter, "{error}"),
Self::DocumentParse(error) => write!(formatter, "{error}"),
Self::Io(error) => write!(formatter, "{error}"),
Self::Item(error) => write!(formatter, "{error}"),
Self::Join(error) => write!(formatter, "{error}"),
Self::Json(error) => write!(formatter, "{error}"),
Self::UrlParse(error) => write!(formatter, "{error}"),
Self::Utf8(error) => write!(formatter, "{error}"),
Self::Validation => write!(formatter, "validation failed"),
}
}
}
impl From<ItemError> for Error {
fn from(error: ItemError) -> Self {
Self::Item(error)
}
}
impl From<AcquireError> for Error {
fn from(error: AcquireError) -> Self {
Self::Acquire(error)
}
}
impl From<CacheError> for Error {
fn from(error: CacheError) -> Self {
Self::Cache(error)
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}
impl From<DocumentParseError> for Error {
fn from(error: DocumentParseError) -> Self {
Self::DocumentParse(error)
}
}
impl From<JoinError> for Error {
fn from(error: JoinError) -> Self {
Self::Join(error)
}
}
impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Self {
Self::Json(error)
}
}
impl From<url::ParseError> for Error {
fn from(error: url::ParseError) -> Self {
Self::UrlParse(error)
}
}
impl From<Utf8Error> for Error {
fn from(error: Utf8Error) -> Self {
Self::Utf8(error)
}
}
#[derive(Debug)]
pub enum ItemError {
Base64(InvalidBase64),
ContentTypeInvalid {
actual: String,
expected: &'static str,
},
DataUrl(DataUrlError),
DocumentParse(DocumentParseError),
ElementNotFound(String),
HttpClient(HttpClientError),
HttpStatus(StatusCode),
InvalidNamespace {
actual: Option<String>,
expected: &'static str,
},
InvalidRootElement {
actual: String,
expected: &'static str,
},
InvalidScheme(String),
Markup(MarkupError),
Sitemap(SitemapError),
UrlParse(ParseError),
Utf8(Utf8Error),
XmlSyntax(String),
}
impl error::Error for ItemError {}
impl Display for ItemError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Base64(error) => write!(formatter, "invalid base64: {error}"),
Self::ContentTypeInvalid { actual, expected } => {
write!(
formatter,
"content type expected {expected} but got {actual}"
)
}
Self::DataUrl(error) => write!(formatter, "{error}"),
Self::DocumentParse(error) => write!(formatter, "{error}"),
Self::ElementNotFound(name) => {
write!(formatter, "element for #{name} not found")
}
Self::HttpClient(error) => write!(formatter, "{error}"),
Self::HttpStatus(status) => write!(formatter, "invalid status {status}"),
Self::InvalidNamespace { actual, expected } => {
write!(
formatter,
"namespace expected {expected} but got {}",
actual.as_deref().unwrap_or("none")
)
}
Self::InvalidRootElement { actual, expected } => {
write!(
formatter,
"root element expected {expected} but got {actual}"
)
}
Self::InvalidScheme(scheme) => write!(formatter, "invalid scheme \"{scheme}\""),
Self::Markup(error) => write!(formatter, "{error}"),
Self::Sitemap(error) => write!(formatter, "{error}"),
Self::UrlParse(error) => write!(formatter, "{error}"),
Self::Utf8(error) => write!(formatter, "{error}"),
Self::XmlSyntax(message) => write!(formatter, "invalid XML: {message}"),
}
}
}
impl Serialize for ItemError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl From<InvalidBase64> for ItemError {
fn from(error: InvalidBase64) -> Self {
Self::Base64(error)
}
}
impl From<DataUrlError> for ItemError {
fn from(error: DataUrlError) -> Self {
Self::DataUrl(error)
}
}
impl From<DocumentParseError> for ItemError {
fn from(error: DocumentParseError) -> Self {
Self::DocumentParse(error)
}
}
impl From<HttpClientError> for ItemError {
fn from(error: HttpClientError) -> Self {
Self::HttpClient(error)
}
}
impl From<url::ParseError> for ItemError {
fn from(error: url::ParseError) -> Self {
Self::UrlParse(error)
}
}
impl From<Utf8Error> for ItemError {
fn from(error: Utf8Error) -> Self {
Self::Utf8(error)
}
}
#[cfg(test)]
mod tests {
use super::*;
use data_url::DataUrl;
#[test]
fn display_item_base64_error() {
assert_eq!(
format!(
"{}",
ItemError::Base64(
DataUrl::process("data:;base64,a")
.unwrap()
.decode_to_vec()
.err()
.unwrap()
)
),
"invalid base64: lone alphabet symbol present"
);
}
#[test]
fn display_item_data_url_error() {
assert_eq!(
format!("{}", ItemError::DataUrl(DataUrlError::NoComma)),
"data url is missing comma delimiting attributes and body"
);
}
#[test]
fn display_item_markup_error() {
assert_eq!(
format!(
"{}",
ItemError::Markup(MarkupError::UnknownTag("foo".into()))
),
"unknown tag \"foo\""
);
}
#[test]
fn display_item_namespace_error() {
assert_eq!(
format!(
"{}",
ItemError::InvalidNamespace {
actual: None,
expected: "http://www.w3.org/2000/svg",
}
),
"namespace expected http://www.w3.org/2000/svg but got none"
);
}
#[test]
fn display_item_root_element_error() {
assert_eq!(
format!(
"{}",
ItemError::InvalidRootElement {
actual: "circle".into(),
expected: "svg",
}
),
"root element expected svg but got circle"
);
}
#[test]
fn display_item_xml_syntax_error() {
assert_eq!(
format!(
"{}",
ItemError::XmlSyntax("Unexpected element in end phase".into())
),
"invalid XML: Unexpected element in end phase"
);
}
}