1use ddex_core::error::DDEXError;
4use ddex_core::ffi::{FFIError, FFIErrorCategory, FFIErrorSeverity};
5use thiserror::Error;
6
7pub use ddex_core::error::ErrorLocation;
9
10pub type Result<T> = std::result::Result<T, ParseError>;
12
13#[derive(Debug, Error, Clone)]
15pub enum ParseError {
16 #[error("XML parsing error: {message}")]
17 XmlError {
18 message: String,
19 location: ErrorLocation,
20 },
21
22 #[error("Unsupported DDEX version: {version}")]
23 UnsupportedVersion { version: String },
24
25 #[error("Security violation: {message}")]
26 SecurityViolation { message: String },
27
28 #[error("Parse timeout after {seconds} seconds")]
29 Timeout { seconds: u64 },
30
31 #[error("Type conversion error: {message}")]
32 ConversionError {
33 message: String,
34 location: ErrorLocation,
35 },
36
37 #[error("Core error: {0}")]
38 Core(#[from] DDEXError),
39
40 #[error("IO error: {message}")]
41 Io { message: String },
42
43 #[error("XML depth limit exceeded: {depth} > {max}")]
44 DepthLimitExceeded { depth: usize, max: usize },
45
46 #[error("Invalid UTF-8 encoding at position {position}: {error}")]
47 InvalidUtf8 { position: usize, error: String },
48
49 #[error("Mismatched XML tags: expected '{expected}', found '{found}' at position {position}")]
50 MismatchedTags {
51 expected: String,
52 found: String,
53 position: usize,
54 },
55
56 #[error("Unexpected closing tag '{tag}' at position {position}")]
57 UnexpectedClosingTag { tag: String, position: usize },
58
59 #[error("Unclosed XML tags at end of document: {tags:?} at position {position}")]
60 UnclosedTags { tags: Vec<String>, position: usize },
61
62 #[error("Malformed XML: {message} at position {position}")]
63 MalformedXml { message: String, position: usize },
64
65 #[error("Invalid XML attribute: {message} at position {position}")]
66 InvalidAttribute { message: String, position: usize },
67
68 #[error("XML parsing error: {0}")]
70 SimpleXmlError(String),
71}
72
73impl From<ParseError> for FFIError {
74 fn from(err: ParseError) -> Self {
75 match err {
76 ParseError::Core(core_err) => core_err.into(),
77 ParseError::XmlError { message, location } => FFIError {
78 code: "PARSE_XML_ERROR".to_string(),
79 message,
80 location: Some(ddex_core::ffi::FFIErrorLocation {
81 line: location.line,
82 column: location.column,
83 path: location.path,
84 }),
85 severity: FFIErrorSeverity::Error,
86 hint: Some("Check XML syntax".to_string()),
87 category: FFIErrorCategory::XmlParsing,
88 },
89 ParseError::UnsupportedVersion { version } => FFIError {
90 code: "UNSUPPORTED_VERSION".to_string(),
91 message: format!("Unsupported DDEX version: {}", version),
92 location: None,
93 severity: FFIErrorSeverity::Error,
94 hint: Some("Use ERN 3.8.2, 4.2, or 4.3".to_string()),
95 category: FFIErrorCategory::Version,
96 },
97 ParseError::SecurityViolation { message } => FFIError {
98 code: "SECURITY_VIOLATION".to_string(),
99 message,
100 location: None,
101 severity: FFIErrorSeverity::Error,
102 hint: Some("Check for XXE or entity expansion attacks".to_string()),
103 category: FFIErrorCategory::Validation,
104 },
105 ParseError::Timeout { seconds } => FFIError {
106 code: "PARSE_TIMEOUT".to_string(),
107 message: format!("Parse timeout after {} seconds", seconds),
108 location: None,
109 severity: FFIErrorSeverity::Error,
110 hint: Some("File may be too large or complex".to_string()),
111 category: FFIErrorCategory::Io,
112 },
113 ParseError::ConversionError { message, location } => FFIError {
114 code: "TYPE_CONVERSION_ERROR".to_string(),
115 message,
116 location: Some(ddex_core::ffi::FFIErrorLocation {
117 line: location.line,
118 column: location.column,
119 path: location.path,
120 }),
121 severity: FFIErrorSeverity::Error,
122 hint: Some("Check builder state and validation".to_string()),
123 category: FFIErrorCategory::Validation,
124 },
125 ParseError::Io { message } => FFIError {
126 code: "IO_ERROR".to_string(),
127 message,
128 location: None,
129 severity: FFIErrorSeverity::Error,
130 hint: None,
131 category: FFIErrorCategory::Io,
132 },
133 ParseError::DepthLimitExceeded { depth, max } => FFIError {
134 code: "DEPTH_LIMIT_EXCEEDED".to_string(),
135 message: format!("XML depth limit exceeded: {} > {}", depth, max),
136 location: None,
137 severity: FFIErrorSeverity::Error,
138 hint: Some("Reduce XML nesting depth to prevent stack overflow".to_string()),
139 category: FFIErrorCategory::Validation,
140 },
141 ParseError::InvalidUtf8 { position, error } => FFIError {
142 code: "INVALID_UTF8".to_string(),
143 message: format!("Invalid UTF-8 encoding at position {}: {}", position, error),
144 location: Some(ddex_core::ffi::FFIErrorLocation {
145 line: 0,
146 column: 0,
147 path: "parser".to_string(),
148 }),
149 severity: FFIErrorSeverity::Error,
150 hint: Some("Ensure the XML file is properly encoded as UTF-8".to_string()),
151 category: FFIErrorCategory::XmlParsing,
152 },
153 ParseError::MismatchedTags {
154 expected,
155 found,
156 position,
157 } => FFIError {
158 code: "MISMATCHED_TAGS".to_string(),
159 message: format!(
160 "Mismatched XML tags: expected '{}', found '{}' at position {}",
161 expected, found, position
162 ),
163 location: Some(ddex_core::ffi::FFIErrorLocation {
164 line: 0,
165 column: 0,
166 path: "parser".to_string(),
167 }),
168 severity: FFIErrorSeverity::Error,
169 hint: Some(format!(
170 "Ensure opening tag '{}' has matching closing tag",
171 expected
172 )),
173 category: FFIErrorCategory::XmlParsing,
174 },
175 ParseError::UnexpectedClosingTag { tag, position } => FFIError {
176 code: "UNEXPECTED_CLOSING_TAG".to_string(),
177 message: format!("Unexpected closing tag '{}' at position {}", tag, position),
178 location: Some(ddex_core::ffi::FFIErrorLocation {
179 line: 0,
180 column: 0,
181 path: "parser".to_string(),
182 }),
183 severity: FFIErrorSeverity::Error,
184 hint: Some(
185 "Remove the unexpected closing tag or add the missing opening tag".to_string(),
186 ),
187 category: FFIErrorCategory::XmlParsing,
188 },
189 ParseError::UnclosedTags { tags, position } => FFIError {
190 code: "UNCLOSED_TAGS".to_string(),
191 message: format!(
192 "Unclosed XML tags at end of document: {:?} at position {}",
193 tags, position
194 ),
195 location: Some(ddex_core::ffi::FFIErrorLocation {
196 line: 0,
197 column: 0,
198 path: "parser".to_string(),
199 }),
200 severity: FFIErrorSeverity::Error,
201 hint: Some(format!("Add closing tags for: {}", tags.join(", "))),
202 category: FFIErrorCategory::XmlParsing,
203 },
204 ParseError::MalformedXml { message, position } => FFIError {
205 code: "MALFORMED_XML".to_string(),
206 message: format!("Malformed XML: {} at position {}", message, position),
207 location: Some(ddex_core::ffi::FFIErrorLocation {
208 line: 0,
209 column: 0,
210 path: "parser".to_string(),
211 }),
212 severity: FFIErrorSeverity::Error,
213 hint: Some("Check XML syntax and structure".to_string()),
214 category: FFIErrorCategory::XmlParsing,
215 },
216 ParseError::InvalidAttribute { message, position } => FFIError {
217 code: "INVALID_ATTRIBUTE".to_string(),
218 message: format!(
219 "Invalid XML attribute: {} at position {}",
220 message, position
221 ),
222 location: Some(ddex_core::ffi::FFIErrorLocation {
223 line: 0,
224 column: 0,
225 path: "parser".to_string(),
226 }),
227 severity: FFIErrorSeverity::Error,
228 hint: Some("Check attribute name and value syntax".to_string()),
229 category: FFIErrorCategory::XmlParsing,
230 },
231 ParseError::SimpleXmlError(message) => FFIError {
232 code: "XML_ERROR".to_string(),
233 message,
234 location: None,
235 severity: FFIErrorSeverity::Error,
236 hint: Some("Check XML syntax".to_string()),
237 category: FFIErrorCategory::XmlParsing,
238 },
239 }
240 }
241}
242
243impl From<std::io::Error> for ParseError {
244 fn from(err: std::io::Error) -> Self {
245 ParseError::Io {
246 message: err.to_string(),
247 }
248 }
249}
250
251impl From<std::str::Utf8Error> for ParseError {
252 fn from(err: std::str::Utf8Error) -> Self {
253 ParseError::XmlError {
254 message: format!("UTF-8 encoding error: {}", err),
255 location: ErrorLocation {
256 line: 0,
257 column: 0,
258 byte_offset: None,
259 path: "parser".to_string(),
260 },
261 }
262 }
263}
264
265impl From<quick_xml::events::attributes::AttrError> for ParseError {
266 fn from(err: quick_xml::events::attributes::AttrError) -> Self {
267 ParseError::XmlError {
268 message: format!("XML attribute error: {}", err),
269 location: ErrorLocation {
270 line: 0,
271 column: 0,
272 byte_offset: None,
273 path: "parser".to_string(),
274 },
275 }
276 }
277}
278
279impl From<quick_xml::Error> for ParseError {
280 fn from(err: quick_xml::Error) -> Self {
281 ParseError::XmlError {
282 message: format!("XML parsing error: {}", err),
283 location: ErrorLocation {
284 line: 0,
285 column: 0,
286 byte_offset: None,
287 path: "parser".to_string(),
288 },
289 }
290 }
291}
292
293impl From<String> for ParseError {
294 fn from(err: String) -> Self {
295 ParseError::XmlError {
296 message: err,
297 location: ErrorLocation {
298 line: 0,
299 column: 0,
300 byte_offset: None,
301 path: "parser".to_string(),
302 },
303 }
304 }
305}