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}. Level must be between 1 and 6."
51            ),
52            WriteError::NewlineInInlineElement(context) => write!(
53                f,
54                "Newline character found within an inline element ({context}) which is not allowed in strict mode or this context."
55            ),
56            WriteError::FmtError(msg) => write!(f, "Formatting error: {msg}"),
57            WriteError::IoError(err) => write!(f, "I/O error: {err}"),
58            WriteError::UnsupportedNodeType => {
59                write!(f, "Unsupported node type encountered during writing.")
60            },
61            WriteError::InvalidStructure(msg) => {
62                write!(f, "Invalid structure: {msg}")
63            },
64            WriteError::InvalidHtmlTag(tag) => {
65                write!(f, "Invalid HTML tag name: '{tag}'. Tag names should only contain alphanumeric characters, underscores, colons, or hyphens.")
66            },
67            WriteError::InvalidHtmlAttribute(attr) => {
68                write!(f, "Invalid HTML attribute name: '{attr}'. Attribute names should only contain alphanumeric characters, underscores, colons, dots, or hyphens.")
69            },
70            WriteError::HtmlRenderingError(html_err) => {
71                write!(f, "Error during HTML rendering phase: {html_err}")
72            },
73            WriteError::HtmlFallbackError(msg) => {
74                write!(f, "Error during HTML fallback rendering: {msg}")
75            },
76            WriteError::Custom { message, code } => {
77                if let Some(code) = code {
78                    write!(f, "Custom error [{code}]: {message}")
79                } else {
80                    write!(f, "Custom error: {message}")
81                }
82            }
83        }
84    }
85}
86
87impl Error for WriteError {}
88
89// Allow converting fmt::Error into WriteError for convenience when using `?`
90impl From<fmt::Error> for WriteError {
91    fn from(err: fmt::Error) -> Self {
92        WriteError::FmtError(err.to_string().into())
93    }
94}
95
96// Allow converting io::Error into WriteError
97impl From<io::Error> for WriteError {
98    fn from(err: io::Error) -> Self {
99        WriteError::IoError(err)
100    }
101}
102
103// Allow converting CoreHtmlWriteError into WriteError
104impl From<CoreHtmlWriteError> for WriteError {
105    fn from(err: CoreHtmlWriteError) -> Self {
106        match err {
107            CoreHtmlWriteError::InvalidHtmlTag(tag) => WriteError::InvalidHtmlTag(tag.into()),
108            CoreHtmlWriteError::InvalidHtmlAttribute(attr) => {
109                WriteError::InvalidHtmlAttribute(attr.into())
110            }
111            other_html_err => WriteError::HtmlRenderingError(other_html_err),
112        }
113    }
114}
115
116/// Result type alias for writer operations.
117pub type WriteResult<T> = Result<T, WriteError>;
118
119/// Convenience methods for creating custom errors
120impl WriteError {
121    /// Create a new custom error with a message
122    pub fn custom<S: Into<EcoString>>(message: S) -> Self {
123        WriteError::Custom {
124            message: message.into(),
125            code: None,
126        }
127    }
128
129    /// Create a new custom error with a message and error code
130    pub fn custom_with_code<S1: Into<EcoString>, S2: Into<EcoString>>(
131        message: S1,
132        code: S2,
133    ) -> Self {
134        WriteError::Custom {
135            message: message.into(),
136            code: Some(code.into()),
137        }
138    }
139}
140
141/// Trait to define custom error factories for WriteError
142///
143/// This trait allows extending WriteError with custom error constructors
144/// while allowing both library and user code to define their own error types.
145pub trait CustomErrorFactory {
146    /// Create an error from this factory
147    fn create_error(&self) -> WriteError;
148}
149
150/// Struct to create structure errors with formatted messages
151pub struct StructureError {
152    /// Format string for the error message
153    format: EcoString,
154    /// Arguments for formatting
155    args: Vec<EcoString>,
156}
157
158impl StructureError {
159    /// Create a new structure error with a format string and arguments
160    pub fn new<S: Into<EcoString>>(format: S) -> Self {
161        Self {
162            format: format.into(),
163            args: Vec::new(),
164        }
165    }
166
167    /// Add an argument to the format string
168    pub fn arg<S: Into<EcoString>>(mut self, arg: S) -> Self {
169        self.args.push(arg.into());
170        self
171    }
172}
173
174impl CustomErrorFactory for StructureError {
175    fn create_error(&self) -> WriteError {
176        let message = match self.args.len() {
177            0 => self.format.clone(),
178            1 => self.format.replace("{}", &self.args[0]),
179            _ => {
180                let mut result = self.format.to_string();
181                for arg in &self.args {
182                    if let Some(pos) = result.find("{}") {
183                        result.replace_range(pos..pos + 2, arg);
184                    }
185                }
186                EcoString::from(result)
187            }
188        };
189
190        WriteError::InvalidStructure(message)
191    }
192}
193
194/// Struct to create custom errors with codes
195pub struct CodedError {
196    /// The error message
197    message: EcoString,
198    /// The error code
199    code: EcoString,
200}
201
202impl CodedError {
203    /// Create a new custom error with message and code
204    pub fn new<S1: Into<EcoString>, S2: Into<EcoString>>(message: S1, code: S2) -> Self {
205        Self {
206            message: message.into(),
207            code: code.into(),
208        }
209    }
210}
211
212impl CustomErrorFactory for CodedError {
213    fn create_error(&self) -> WriteError {
214        WriteError::custom_with_code(&self.message, &self.code)
215    }
216}
217
218/// Extensions for Result<T, WriteError> to work with custom error factories
219pub trait WriteResultExt<T> {
220    /// Convert a custom error factory into an Err result
221    fn custom_error<F: CustomErrorFactory>(factory: F) -> Result<T, WriteError>;
222}
223
224impl<T> WriteResultExt<T> for Result<T, WriteError> {
225    fn custom_error<F: CustomErrorFactory>(factory: F) -> Result<T, WriteError> {
226        Err(factory.create_error())
227    }
228}