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 std::error::Error;
7use std::fmt::{self, Display};
8
9/// Errors that can occur during CommonMark writing.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum WriteError {
12    /// An invalid heading level was encountered (must be 1-6).
13    InvalidHeadingLevel(u8),
14    /// 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).
15    NewlineInInlineElement(String),
16    /// An underlying formatting error occurred.
17    FmtError(String),
18    /// An unsupported node type was encountered.
19    UnsupportedNodeType,
20    /// Invalid structure in a node (e.g., mismatched table columns)
21    InvalidStructure(String),
22    /// An invalid HTML tag was found (contains unsafe characters)
23    InvalidHtmlTag(String),
24    /// An invalid HTML attribute was found (contains unsafe characters)
25    InvalidHtmlAttribute(String),
26    /// A custom error with a message and optional error code.
27    Custom {
28        /// Custom error message
29        message: String,
30        /// Optional error code for programmatic identification
31        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
74// Allow converting fmt::Error into WriteError for convenience when using `?`
75impl From<fmt::Error> for WriteError {
76    fn from(err: fmt::Error) -> Self {
77        WriteError::FmtError(err.to_string())
78    }
79}
80
81/// Result type alias for writer operations.
82pub type WriteResult<T> = Result<T, WriteError>;
83
84/// Convenience methods for creating custom errors
85impl WriteError {
86    /// Create a new custom error with a message
87    pub fn custom<S: Into<String>>(message: S) -> Self {
88        WriteError::Custom {
89            message: message.into(),
90            code: None,
91        }
92    }
93
94    /// Create a new custom error with a message and error code
95    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
103/// Trait to define custom error factories for WriteError
104///
105/// This trait allows extending WriteError with custom error constructors
106/// while allowing both library and user code to define their own error types.
107pub trait CustomErrorFactory {
108    /// Create an error from this factory
109    fn create_error(&self) -> WriteError;
110}
111
112/// Struct to create structure errors with formatted messages
113pub struct StructureError {
114    /// Format string for the error message
115    format: String,
116    /// Arguments for formatting
117    args: Vec<String>,
118}
119
120impl StructureError {
121    /// Create a new structure error with a format string and arguments
122    pub fn new<S: Into<String>>(format: S) -> Self {
123        Self {
124            format: format.into(),
125            args: Vec::new(),
126        }
127    }
128
129    /// Add an argument to the format string
130    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
156/// Struct to create custom errors with codes
157pub struct CodedError {
158    /// The error message
159    message: String,
160    /// The error code
161    code: String,
162}
163
164impl CodedError {
165    /// Create a new custom error with message and code
166    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
180/// Extensions for Result<T, WriteError> to work with custom error factories
181pub trait WriteResultExt<T> {
182    /// Convert a custom error factory into an Err result
183    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}