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