cmark_writer/
error.rs

1//! Error handling for CommonMark writer.
2//!
3//! This module provides error types and implementations for handling errors
4//! that can occur during CommonMark writing.
5
6use ecow::EcoString;
7
8use crate::writer::html::error::HtmlWriteError as CoreHtmlWriteError;
9use std::error::Error;
10use std::fmt::{self, Display};
11use std::io;
12
13/// Errors that can occur during CommonMark writing.
14#[derive(Debug)]
15pub enum WriteError {
16    /// An invalid heading level was encountered (must be 1-6).
17    InvalidHeadingLevel(u8),
18    /// A newline character was found in an inline element where it's not allowed (e.g., in strict mode or specific contexts like table cells, link text, image alt text).
19    NewlineInInlineElement(EcoString),
20    /// An underlying formatting error occurred.
21    FmtError(EcoString),
22    /// An underlying I/O error occurred.
23    IoError(io::Error),
24    /// An unsupported node type was encountered.
25    UnsupportedNodeType,
26    /// Invalid structure in a node (e.g., mismatched table columns)
27    InvalidStructure(EcoString),
28    /// An invalid HTML tag was found (contains unsafe characters)
29    InvalidHtmlTag(EcoString),
30    /// An invalid HTML attribute was found (contains unsafe characters)
31    InvalidHtmlAttribute(EcoString),
32    /// An error occurred during dedicated HTML rendering.
33    HtmlRenderingError(CoreHtmlWriteError),
34    /// An error occurred during HTML fallback rendering for tables with block elements.
35    HtmlFallbackError(EcoString),
36    /// A custom error with a message and optional error code.
37    Custom {
38        /// Custom error message
39        message: EcoString,
40        /// Optional error code for programmatic identification
41        code: Option<EcoString>,
42    },
43}
44
45impl Display for WriteError {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        match self {
48            WriteError::InvalidHeadingLevel(level) => write!(
49                f,
50                "Invalid heading level: {}. Level must be between 1 and 6.",
51                level
52            ),
53            WriteError::NewlineInInlineElement(context) => write!(
54                f,
55                "Newline character found within an inline element ({}) which is not allowed in strict mode or this context.",
56                context
57            ),
58            WriteError::FmtError(msg) => write!(f, "Formatting error: {}", msg),
59            WriteError::IoError(err) => write!(f, "I/O error: {}", err),
60            WriteError::UnsupportedNodeType => {
61                write!(f, "Unsupported node type encountered during writing.")
62            },
63            WriteError::InvalidStructure(msg) => {
64                write!(f, "Invalid structure: {}", msg)
65            },
66            WriteError::InvalidHtmlTag(tag) => {
67                write!(f, "Invalid HTML tag name: '{}'. Tag names should only contain alphanumeric characters, underscores, colons, or hyphens.", tag)
68            },
69            WriteError::InvalidHtmlAttribute(attr) => {
70                write!(f, "Invalid HTML attribute name: '{}'. Attribute names should only contain alphanumeric characters, underscores, colons, dots, or hyphens.", attr)
71            },
72            WriteError::HtmlRenderingError(html_err) => {
73                write!(f, "Error during HTML rendering phase: {}", html_err)
74            },
75            WriteError::HtmlFallbackError(msg) => {
76                write!(f, "Error during HTML fallback rendering: {}", msg)
77            },
78            WriteError::Custom { message, code } => {
79                if let Some(code) = code {
80                    write!(f, "Custom error [{}]: {}", code, message)
81                } else {
82                    write!(f, "Custom error: {}", message)
83                }
84            }
85        }
86    }
87}
88
89impl Error for WriteError {}
90
91// Allow converting fmt::Error into WriteError for convenience when using `?`
92impl From<fmt::Error> for WriteError {
93    fn from(err: fmt::Error) -> Self {
94        WriteError::FmtError(err.to_string().into())
95    }
96}
97
98// Allow converting io::Error into WriteError
99impl From<io::Error> for WriteError {
100    fn from(err: io::Error) -> Self {
101        WriteError::IoError(err)
102    }
103}
104
105// Allow converting CoreHtmlWriteError into WriteError
106impl From<CoreHtmlWriteError> for WriteError {
107    fn from(err: CoreHtmlWriteError) -> Self {
108        match err {
109            CoreHtmlWriteError::InvalidHtmlTag(tag) => WriteError::InvalidHtmlTag(tag.into()),
110            CoreHtmlWriteError::InvalidHtmlAttribute(attr) => {
111                WriteError::InvalidHtmlAttribute(attr.into())
112            }
113            other_html_err => WriteError::HtmlRenderingError(other_html_err),
114        }
115    }
116}
117
118/// Result type alias for writer operations.
119pub type WriteResult<T> = Result<T, WriteError>;
120
121/// Convenience methods for creating custom errors
122impl WriteError {
123    /// Create a new custom error with a message
124    pub fn custom<S: Into<EcoString>>(message: S) -> Self {
125        WriteError::Custom {
126            message: message.into(),
127            code: None,
128        }
129    }
130
131    /// Create a new custom error with a message and error code
132    pub fn custom_with_code<S1: Into<EcoString>, S2: Into<EcoString>>(
133        message: S1,
134        code: S2,
135    ) -> Self {
136        WriteError::Custom {
137            message: message.into(),
138            code: Some(code.into()),
139        }
140    }
141}
142
143/// Trait to define custom error factories for WriteError
144///
145/// This trait allows extending WriteError with custom error constructors
146/// while allowing both library and user code to define their own error types.
147pub trait CustomErrorFactory {
148    /// Create an error from this factory
149    fn create_error(&self) -> WriteError;
150}
151
152/// Struct to create structure errors with formatted messages
153pub struct StructureError {
154    /// Format string for the error message
155    format: EcoString,
156    /// Arguments for formatting
157    args: Vec<EcoString>,
158}
159
160impl StructureError {
161    /// Create a new structure error with a format string and arguments
162    pub fn new<S: Into<EcoString>>(format: S) -> Self {
163        Self {
164            format: format.into(),
165            args: Vec::new(),
166        }
167    }
168
169    /// Add an argument to the format string
170    pub fn arg<S: Into<EcoString>>(mut self, arg: S) -> Self {
171        self.args.push(arg.into());
172        self
173    }
174}
175
176impl CustomErrorFactory for StructureError {
177    fn create_error(&self) -> WriteError {
178        let message = match self.args.len() {
179            0 => self.format.clone(),
180            1 => self.format.replace("{}", &self.args[0]),
181            _ => {
182                let mut result = self.format.to_string();
183                for arg in &self.args {
184                    if let Some(pos) = result.find("{}") {
185                        result.replace_range(pos..pos + 2, arg);
186                    }
187                }
188                EcoString::from(result)
189            }
190        };
191
192        WriteError::InvalidStructure(message)
193    }
194}
195
196/// Struct to create custom errors with codes
197pub struct CodedError {
198    /// The error message
199    message: EcoString,
200    /// The error code
201    code: EcoString,
202}
203
204impl CodedError {
205    /// Create a new custom error with message and code
206    pub fn new<S1: Into<EcoString>, S2: Into<EcoString>>(message: S1, code: S2) -> Self {
207        Self {
208            message: message.into(),
209            code: code.into(),
210        }
211    }
212}
213
214impl CustomErrorFactory for CodedError {
215    fn create_error(&self) -> WriteError {
216        WriteError::custom_with_code(&self.message, &self.code)
217    }
218}
219
220/// Extensions for Result<T, WriteError> to work with custom error factories
221pub trait WriteResultExt<T> {
222    /// Convert a custom error factory into an Err result
223    fn custom_error<F: CustomErrorFactory>(factory: F) -> Result<T, WriteError>;
224}
225
226impl<T> WriteResultExt<T> for Result<T, WriteError> {
227    fn custom_error<F: CustomErrorFactory>(factory: F) -> Result<T, WriteError> {
228        Err(factory.create_error())
229    }
230}