1use std::error::Error;
7use std::fmt::{self, Display};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum WriteError {
12 InvalidHeadingLevel(u8),
14 NewlineInInlineElement(String),
16 FmtError(String),
18 UnsupportedNodeType,
20 InvalidStructure(String),
22 InvalidHtmlTag(String),
24 InvalidHtmlAttribute(String),
26 Custom {
28 message: String,
30 code: Option<String>,
32 },
33}
34
35impl Display for WriteError {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 WriteError::InvalidHeadingLevel(level) => write!(
39 f,
40 "Invalid heading level: {}. Level must be between 1 and 6.",
41 level
42 ),
43 WriteError::NewlineInInlineElement(context) => write!(
44 f,
45 "Newline character found within an inline element ({}) which is not allowed in strict mode or this context.",
46 context
47 ),
48 WriteError::FmtError(msg) => write!(f, "Formatting error: {}", msg),
49 WriteError::UnsupportedNodeType => {
50 write!(f, "Unsupported node type encountered during writing.")
51 },
52 WriteError::InvalidStructure(msg) => {
53 write!(f, "Invalid structure: {}", msg)
54 },
55 WriteError::InvalidHtmlTag(tag) => {
56 write!(f, "Invalid HTML tag name: '{}'. Tag names should only contain alphanumeric characters, underscores, colons, or hyphens.", tag)
57 },
58 WriteError::InvalidHtmlAttribute(attr) => {
59 write!(f, "Invalid HTML attribute name: '{}'. Attribute names should only contain alphanumeric characters, underscores, colons, dots, or hyphens.", attr)
60 },
61 WriteError::Custom { message, code } => {
62 if let Some(code) = code {
63 write!(f, "Custom error [{}]: {}", code, message)
64 } else {
65 write!(f, "Custom error: {}", message)
66 }
67 }
68 }
69 }
70}
71
72impl Error for WriteError {}
73
74impl From<fmt::Error> for WriteError {
76 fn from(err: fmt::Error) -> Self {
77 WriteError::FmtError(err.to_string())
78 }
79}
80
81pub type WriteResult<T> = Result<T, WriteError>;
83
84impl WriteError {
86 pub fn custom<S: Into<String>>(message: S) -> Self {
88 WriteError::Custom {
89 message: message.into(),
90 code: None,
91 }
92 }
93
94 pub fn custom_with_code<S1: Into<String>, S2: Into<String>>(message: S1, code: S2) -> Self {
96 WriteError::Custom {
97 message: message.into(),
98 code: Some(code.into()),
99 }
100 }
101}
102
103pub trait CustomErrorFactory {
108 fn create_error(&self) -> WriteError;
110}
111
112pub struct StructureError {
114 format: String,
116 args: Vec<String>,
118}
119
120impl StructureError {
121 pub fn new<S: Into<String>>(format: S) -> Self {
123 Self {
124 format: format.into(),
125 args: Vec::new(),
126 }
127 }
128
129 pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
131 self.args.push(arg.into());
132 self
133 }
134}
135
136impl CustomErrorFactory for StructureError {
137 fn create_error(&self) -> WriteError {
138 let message = match self.args.len() {
139 0 => self.format.clone(),
140 1 => self.format.replace("{}", &self.args[0]),
141 _ => {
142 let mut result = self.format.clone();
143 for arg in &self.args {
144 if let Some(pos) = result.find("{}") {
145 result.replace_range(pos..pos + 2, arg);
146 }
147 }
148 result
149 }
150 };
151
152 WriteError::InvalidStructure(message)
153 }
154}
155
156pub struct CodedError {
158 message: String,
160 code: String,
162}
163
164impl CodedError {
165 pub fn new<S1: Into<String>, S2: Into<String>>(message: S1, code: S2) -> Self {
167 Self {
168 message: message.into(),
169 code: code.into(),
170 }
171 }
172}
173
174impl CustomErrorFactory for CodedError {
175 fn create_error(&self) -> WriteError {
176 WriteError::custom_with_code(&self.message, &self.code)
177 }
178}
179
180pub trait WriteResultExt<T> {
182 fn custom_error<F: CustomErrorFactory>(factory: F) -> Result<T, WriteError>;
184}
185
186impl<T> WriteResultExt<T> for Result<T, WriteError> {
187 fn custom_error<F: CustomErrorFactory>(factory: F) -> Result<T, WriteError> {
188 Err(factory.create_error())
189 }
190}