1use log;
7use quick_xml;
8use std::string::FromUtf8Error;
9use thiserror::Error;
10
11#[derive(Debug, Error)]
13#[non_exhaustive]
14pub enum RssError {
15 #[error("XML error occurred: {0}")]
17 XmlWriteError(#[from] quick_xml::Error),
18
19 #[error("XML parse error occurred: {0}")]
21 XmlParseError(quick_xml::Error),
22
23 #[error("UTF-8 conversion error occurred: {0}")]
25 Utf8Error(#[from] FromUtf8Error),
26
27 #[error("A required field is missing: {0}")]
29 MissingField(String),
30
31 #[error("Date parse error: {0}")]
33 DateParseError(String),
34
35 #[error("I/O error occurred: {0}")]
37 IoError(#[from] std::io::Error),
38
39 #[error("Invalid input data provided: {0}")]
41 InvalidInput(String),
42
43 #[error("Invalid URL provided: {0}")]
45 InvalidUrl(String),
46
47 #[error("Unknown XML element found: {0}")]
49 UnknownElement(String),
50
51 #[error("Validation errors: {0:?}")]
53 ValidationErrors(Vec<String>),
54
55 #[error("Date sort error: {0:?}")]
57 DateSortError(Vec<DateSortError>),
58
59 #[error("Item validation error: {0}")]
61 ItemValidationError(String),
62
63 #[error("Unknown field encountered: {0}")]
65 UnknownField(String),
66
67 #[error("Custom error: {0}")]
69 Custom(String),
70
71 #[error("Invalid RSS version: {0}")]
73 InvalidRssVersion(String),
74 }
83
84#[derive(Debug, Error)]
86#[non_exhaustive]
87#[error("Validation error: {message}")]
88pub struct ValidationError {
89 pub field: String,
91 pub message: String,
93}
94
95#[derive(Debug, Error)]
97#[non_exhaustive]
98#[error("Date sort error: {message}")]
99pub struct DateSortError {
100 pub index: usize,
102 pub message: String,
104}
105
106pub type Result<T> = std::result::Result<T, RssError>;
111
112impl RssError {
113 pub fn missing_field<S: Into<String>>(field_name: S) -> Self {
123 RssError::MissingField(field_name.into())
124 }
125
126 pub fn date_sort_error<S: Into<String>>(
137 index: usize,
138 message: S,
139 ) -> DateSortError {
140 DateSortError {
141 index,
142 message: message.into(),
143 }
144 }
145
146 pub fn invalid_input<S: Into<String>>(message: S) -> Self {
156 RssError::InvalidInput(message.into())
157 }
158
159 pub fn custom<S: Into<String>>(message: S) -> Self {
169 RssError::Custom(message.into())
170 }
171
172 pub fn log(&self) {
177 log::error!("RSS Error occurred: {}", self);
178 }
179
180 #[must_use]
188 pub fn to_http_status(&self) -> u16 {
189 match self {
190 RssError::XmlWriteError(_)
192 | RssError::XmlParseError(_)
193 | RssError::Utf8Error(_)
194 | RssError::IoError(_)
195 | RssError::UnknownElement(_)
196 | RssError::DateSortError(_)
197 | RssError::UnknownField(_)
198 | RssError::Custom(_) => 500,
199
200 RssError::MissingField(_)
202 | RssError::InvalidInput(_)
203 | RssError::DateParseError(_)
204 | RssError::InvalidUrl(_)
205 | RssError::ValidationErrors(_)
206 | RssError::ItemValidationError(_)
207 | RssError::InvalidRssVersion(_) => 400,
208 }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use std::error::Error;
216 use std::io;
217
218 #[test]
219 fn test_rss_error_display() {
220 let error = RssError::missing_field("title");
221 assert_eq!(
222 error.to_string(),
223 "A required field is missing: title"
224 );
225 }
226
227 #[test]
228 fn test_xml_write_error() {
229 let xml_error = quick_xml::Error::Io(std::sync::Arc::new(
230 io::Error::new(io::ErrorKind::Other, "XML error"),
231 ));
232 let error = RssError::XmlWriteError(xml_error);
233 assert_eq!(
234 error.to_string(),
235 "XML error occurred: I/O error: XML error"
236 );
237 }
238
239 #[test]
240 fn test_utf8_error() {
241 let utf8_error =
242 String::from_utf8(vec![0, 159, 146, 150]).unwrap_err();
243 let error = RssError::Utf8Error(utf8_error);
244 assert_eq!(error.to_string(), "UTF-8 conversion error occurred: invalid utf-8 sequence of 1 bytes from index 1");
245 }
246
247 #[test]
248 fn test_io_error() {
249 let io_error =
250 io::Error::new(io::ErrorKind::NotFound, "File not found");
251 let error: RssError = io_error.into();
252 assert_eq!(
253 error.to_string(),
254 "I/O error occurred: File not found"
255 );
256 }
257
258 #[test]
259 fn test_error_is_send_and_sync() {
260 fn assert_send_sync<T: Send + Sync>() {}
261 assert_send_sync::<RssError>();
262 }
263
264 #[test]
265 fn test_error_source() {
266 let xml_error = quick_xml::Error::Io(std::sync::Arc::new(
267 io::Error::new(io::ErrorKind::NotFound, "File not found"),
268 ));
269 let error = RssError::XmlWriteError(xml_error);
270 assert!(error.source().is_some());
271
272 let io_error: RssError =
273 io::Error::new(io::ErrorKind::NotFound, "File not found")
274 .into();
275 assert!(io_error.source().is_some());
276 }
277
278 #[test]
279 fn test_missing_field_with_string() {
280 let error = RssError::missing_field(String::from("author"));
281 assert_eq!(
282 error.to_string(),
283 "A required field is missing: author"
284 );
285 }
286
287 #[test]
288 fn test_missing_field_with_str() {
289 let error = RssError::missing_field("description");
290 assert_eq!(
291 error.to_string(),
292 "A required field is missing: description"
293 );
294 }
295
296 #[test]
297 fn test_error_downcast() {
298 let error: Box<dyn Error> =
299 Box::new(RssError::missing_field("category"));
300 let downcast_result = error.downcast::<RssError>();
301 assert!(downcast_result.is_ok());
302 }
303
304 #[test]
305 fn test_invalid_input_error() {
306 let error = RssError::invalid_input("Invalid date format");
307 assert_eq!(
308 error.to_string(),
309 "Invalid input data provided: Invalid date format"
310 );
311 }
312
313 #[test]
314 fn test_custom_error() {
315 let error = RssError::custom("Unforeseen error occurred");
316 assert_eq!(
317 error.to_string(),
318 "Custom error: Unforeseen error occurred"
319 );
320 }
321
322 #[test]
323 fn test_to_http_status() {
324 assert_eq!(
325 RssError::missing_field("title").to_http_status(),
326 400
327 );
328 assert_eq!(
329 RssError::XmlWriteError(quick_xml::Error::Io(
330 std::sync::Arc::new(io::Error::new(
331 io::ErrorKind::Other,
332 "XML error"
333 ))
334 ))
335 .to_http_status(),
336 500
337 );
338 assert_eq!(
339 RssError::InvalidInput("Bad input".to_string())
340 .to_http_status(),
341 400
342 );
343 }
344
345 #[test]
346 fn test_validation_error() {
347 let error = ValidationError {
348 field: "some_field".to_string(),
349 message: "Invalid field".to_string(),
350 };
351 assert_eq!(
352 error.to_string(),
353 "Validation error: Invalid field"
354 );
355 }
356
357 #[test]
358 fn test_date_sort_error() {
359 let error = DateSortError {
360 index: 0,
361 message: "Invalid date".to_string(),
362 };
363 assert_eq!(error.to_string(), "Date sort error: Invalid date");
364 }
365
366 #[test]
367 fn test_missing_field_error() {
368 let rss_error = RssError::MissingField("title".to_string());
369
370 assert_eq!(
371 format!("{}", rss_error),
372 "A required field is missing: title"
373 );
374 }
375
376 #[test]
377 fn test_date_parse_error() {
378 let rss_error =
379 RssError::DateParseError("Invalid date format".to_string());
380
381 assert_eq!(
382 format!("{}", rss_error),
383 "Date parse error: Invalid date format"
384 );
385 }
386
387 #[test]
388 fn test_invalid_url_error() {
389 let rss_error =
390 RssError::InvalidUrl("https://invalid-url".to_string());
391
392 assert_eq!(
393 format!("{}", rss_error),
394 "Invalid URL provided: https://invalid-url"
395 );
396 }
397
398 #[test]
399 fn test_unknown_element_error() {
400 let rss_error =
401 RssError::UnknownElement("unknown-element".to_string());
402
403 assert_eq!(
404 format!("{}", rss_error),
405 "Unknown XML element found: unknown-element"
406 );
407 }
408
409 #[test]
410 fn test_validation_errors() {
411 let validation_errors = vec![
412 "Title is missing".to_string(),
413 "Invalid pub date".to_string(),
414 ];
415 let rss_error =
416 RssError::ValidationErrors(validation_errors.clone());
417
418 assert_eq!(
419 format!("{}", rss_error),
420 format!("Validation errors: {:?}", validation_errors)
421 );
422 }
423}