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