use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum HtmlError {
#[error("Failed to compile regex: {0}")]
RegexCompilationError(#[from] regex::Error),
#[error("Failed to extract front matter: {0}")]
FrontMatterExtractionError(String),
#[error("Failed to format header: {0}")]
HeaderFormattingError(String),
#[error("Failed to parse selector '{0}': {1}")]
SelectorParseError(String, String),
#[error("Failed to minify HTML: {0}")]
MinificationError(String),
#[error("Failed to convert Markdown to HTML: {message}")]
MarkdownConversion {
message: String,
#[source]
source: Option<io::Error>,
},
#[error("HTML minification failed: {message}")]
Minification {
message: String,
size: Option<usize>,
#[source]
source: Option<io::Error>,
},
#[error("SEO optimization failed: {kind}: {message}")]
Seo {
kind: SeoErrorKind,
message: String,
element: Option<String>,
},
#[error("Accessibility check failed: {kind}: {message}")]
Accessibility {
kind: ErrorKind,
message: String,
wcag_guideline: Option<String>,
},
#[error("Missing required HTML element: {0}")]
MissingHtmlElement(String),
#[error("Invalid structured data: {0}")]
InvalidStructuredData(String),
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Invalid front matter format: {0}")]
InvalidFrontMatterFormat(String),
#[error("Input too large: size {0} bytes")]
InputTooLarge(usize),
#[error("Invalid header format: {0}")]
InvalidHeaderFormat(String),
#[error("UTF-8 conversion error: {0}")]
Utf8ConversionError(#[from] std::string::FromUtf8Error),
#[error("Parsing error: {0}")]
ParsingError(String),
#[error("Template rendering failed: {message}")]
TemplateRendering {
message: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Validation error: {0}")]
ValidationError(String),
#[error("Unexpected error: {0}")]
UnexpectedError(String),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SeoErrorKind {
MissingMetaTags,
InvalidInput,
InvalidStructuredData,
MissingTitle,
MissingDescription,
Other,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ErrorKind {
MissingAriaAttributes,
InvalidAriaValue,
MissingAltText,
HeadingStructure,
MissingFormLabels,
Other,
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::MissingAriaAttributes => {
write!(f, "Missing ARIA attributes")
}
ErrorKind::InvalidAriaValue => {
write!(f, "Invalid ARIA attribute values")
}
ErrorKind::MissingAltText => {
write!(f, "Missing alternative text")
}
ErrorKind::HeadingStructure => {
write!(f, "Incorrect heading structure")
}
ErrorKind::MissingFormLabels => {
write!(f, "Missing form labels")
}
ErrorKind::Other => {
write!(f, "Other accessibility-related errors")
}
}
}
}
impl std::fmt::Display for SeoErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SeoErrorKind::MissingMetaTags => {
write!(f, "Missing required meta tags")
}
SeoErrorKind::InvalidStructuredData => {
write!(f, "Invalid structured data")
}
SeoErrorKind::MissingTitle => write!(f, "Missing title"),
SeoErrorKind::InvalidInput => write!(f, "Invalid input"),
SeoErrorKind::MissingDescription => {
write!(f, "Missing description")
}
SeoErrorKind::Other => {
write!(f, "Other SEO-related errors")
}
}
}
}
impl HtmlError {
pub fn invalid_input(
message: impl Into<String>,
_input: Option<String>,
) -> Self {
Self::InvalidInput(message.into())
}
pub fn input_too_large(size: usize) -> Self {
Self::InputTooLarge(size)
}
pub fn seo(
kind: SeoErrorKind,
message: impl Into<String>,
element: Option<String>,
) -> Self {
Self::Seo {
kind,
message: message.into(),
element,
}
}
pub fn accessibility(
kind: ErrorKind,
message: impl Into<String>,
wcag_guideline: Option<String>,
) -> Self {
Self::Accessibility {
kind,
message: message.into(),
wcag_guideline,
}
}
pub fn markdown_conversion(
message: impl Into<String>,
source: Option<io::Error>,
) -> Self {
Self::MarkdownConversion {
message: message.into(),
source,
}
}
}
pub type Result<T> = std::result::Result<T, HtmlError>;
#[cfg(test)]
mod tests {
use super::*;
mod basic_errors {
use super::*;
#[test]
fn test_regex_compilation_error() {
let regex_error =
regex::Error::Syntax("invalid regex".to_string());
let error: HtmlError = regex_error.into();
assert!(matches!(
error,
HtmlError::RegexCompilationError(_)
));
assert!(error
.to_string()
.contains("Failed to compile regex"));
}
#[test]
fn test_front_matter_extraction_error() {
let error = HtmlError::FrontMatterExtractionError(
"Missing delimiter".to_string(),
);
assert_eq!(
error.to_string(),
"Failed to extract front matter: Missing delimiter"
);
}
#[test]
fn test_header_formatting_error() {
let error = HtmlError::HeaderFormattingError(
"Invalid header level".to_string(),
);
assert_eq!(
error.to_string(),
"Failed to format header: Invalid header level"
);
}
#[test]
fn test_selector_parse_error() {
let error = HtmlError::SelectorParseError(
"div>".to_string(),
"Unexpected end".to_string(),
);
assert_eq!(
error.to_string(),
"Failed to parse selector 'div>': Unexpected end"
);
}
#[test]
fn test_minification_error() {
let error = HtmlError::MinificationError(
"Syntax error".to_string(),
);
assert_eq!(
error.to_string(),
"Failed to minify HTML: Syntax error"
);
}
}
mod structured_errors {
use super::*;
#[test]
fn test_markdown_conversion_with_source() {
let source =
io::Error::new(io::ErrorKind::Other, "source error");
let error = HtmlError::markdown_conversion(
"Conversion failed",
Some(source),
);
assert!(error
.to_string()
.contains("Failed to convert Markdown to HTML"));
}
#[test]
fn test_markdown_conversion_without_source() {
let error = HtmlError::markdown_conversion(
"Conversion failed",
None,
);
assert!(error.to_string().contains("Conversion failed"));
}
#[test]
fn test_minification_with_size_and_source() {
let error = HtmlError::Minification {
message: "Too large".to_string(),
size: Some(1024),
source: Some(io::Error::new(
io::ErrorKind::Other,
"IO error",
)),
};
assert!(error
.to_string()
.contains("HTML minification failed"));
}
}
mod seo_errors {
use super::*;
#[test]
fn test_seo_error_missing_meta_tags() {
let error = HtmlError::seo(
SeoErrorKind::MissingMetaTags,
"Required meta tags missing",
Some("head".to_string()),
);
assert!(error
.to_string()
.contains("Missing required meta tags"));
}
#[test]
fn test_seo_error_without_element() {
let error = HtmlError::seo(
SeoErrorKind::MissingTitle,
"Title not found",
None,
);
assert!(error.to_string().contains("Missing title"));
}
#[test]
fn test_all_seo_error_kinds() {
let kinds = [
SeoErrorKind::MissingMetaTags,
SeoErrorKind::InvalidStructuredData,
SeoErrorKind::MissingTitle,
SeoErrorKind::MissingDescription,
SeoErrorKind::Other,
];
for kind in kinds {
assert!(!kind.to_string().is_empty());
}
}
}
mod accessibility_errors {
use super::*;
#[test]
fn test_accessibility_error_with_guideline() {
let error = HtmlError::accessibility(
ErrorKind::MissingAltText,
"Images must have alt text",
Some("WCAG 1.1.1".to_string()),
);
assert!(error
.to_string()
.contains("Missing alternative text"));
}
#[test]
fn test_accessibility_error_without_guideline() {
let error = HtmlError::accessibility(
ErrorKind::InvalidAriaValue,
"Invalid ARIA value",
None,
);
assert!(error
.to_string()
.contains("Invalid ARIA attribute values"));
}
#[test]
fn test_all_accessibility_error_kinds() {
let kinds = [
ErrorKind::MissingAriaAttributes,
ErrorKind::InvalidAriaValue,
ErrorKind::MissingAltText,
ErrorKind::HeadingStructure,
ErrorKind::MissingFormLabels,
ErrorKind::Other,
];
for kind in kinds {
assert!(!kind.to_string().is_empty());
}
}
}
mod io_errors {
use super::*;
#[test]
fn test_io_error_kinds() {
let error_kinds = [
io::ErrorKind::NotFound,
io::ErrorKind::PermissionDenied,
io::ErrorKind::ConnectionRefused,
io::ErrorKind::ConnectionReset,
io::ErrorKind::ConnectionAborted,
io::ErrorKind::NotConnected,
io::ErrorKind::AddrInUse,
io::ErrorKind::AddrNotAvailable,
io::ErrorKind::BrokenPipe,
io::ErrorKind::AlreadyExists,
io::ErrorKind::WouldBlock,
io::ErrorKind::InvalidInput,
io::ErrorKind::InvalidData,
io::ErrorKind::TimedOut,
io::ErrorKind::WriteZero,
io::ErrorKind::Interrupted,
io::ErrorKind::Unsupported,
io::ErrorKind::UnexpectedEof,
io::ErrorKind::OutOfMemory,
io::ErrorKind::Other,
];
for kind in error_kinds {
let io_error = io::Error::new(kind, "test error");
let html_error: HtmlError = io_error.into();
assert!(matches!(html_error, HtmlError::Io(_)));
}
}
}
mod helper_methods {
use super::*;
#[test]
fn test_invalid_input_with_content() {
let error = HtmlError::invalid_input(
"Bad input",
Some("problematic content".to_string()),
);
assert!(error.to_string().contains("Invalid input"));
}
#[test]
fn test_input_too_large() {
let error = HtmlError::input_too_large(1024);
assert!(error.to_string().contains("1024 bytes"));
}
#[test]
fn test_template_rendering_error() {
let source_error = Box::new(io::Error::new(
io::ErrorKind::Other,
"render failed",
));
let error = HtmlError::TemplateRendering {
message: "Template error".to_string(),
source: source_error,
};
assert!(error
.to_string()
.contains("Template rendering failed"));
}
}
mod misc_errors {
use super::*;
#[test]
fn test_missing_html_element() {
let error =
HtmlError::MissingHtmlElement("title".to_string());
assert!(error
.to_string()
.contains("Missing required HTML element"));
}
#[test]
fn test_invalid_structured_data() {
let error = HtmlError::InvalidStructuredData(
"Invalid JSON-LD".to_string(),
);
assert!(error
.to_string()
.contains("Invalid structured data"));
}
#[test]
fn test_invalid_front_matter_format() {
let error = HtmlError::InvalidFrontMatterFormat(
"Missing closing delimiter".to_string(),
);
assert!(error
.to_string()
.contains("Invalid front matter format"));
}
#[test]
fn test_parsing_error() {
let error =
HtmlError::ParsingError("Unexpected token".to_string());
assert!(error.to_string().contains("Parsing error"));
}
#[test]
fn test_validation_error() {
let error = HtmlError::ValidationError(
"Schema validation failed".to_string(),
);
assert!(error.to_string().contains("Validation error"));
}
#[test]
fn test_unexpected_error() {
let error = HtmlError::UnexpectedError(
"Something went wrong".to_string(),
);
assert!(error.to_string().contains("Unexpected error"));
}
}
}