1use serde::de::Error as SerdeError;
7use serde_yml::Error as SerdeYmlError;
8use std::fmt::Display;
9use thiserror::Error;
10
11#[derive(Debug)]
15pub struct ContextError {
16 context: String,
18 source: Box<dyn std::error::Error + Send + Sync>,
20}
21
22impl std::fmt::Display for ContextError {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 write!(f, "{}: {}", self.context, self.source)
26 }
27}
28
29impl std::error::Error for ContextError {
31 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32 Some(&*self.source)
33 }
34}
35
36#[derive(Error, Debug)]
41pub enum MetadataError {
42 #[error("Failed to extract metadata: {message}")]
44 ExtractionError {
45 message: String,
47 },
48
49 #[error("Failed to process metadata: {message}")]
51 ProcessingError {
52 message: String,
54 },
55
56 #[error("Missing required metadata field: {0}")]
58 MissingFieldError(String),
59
60 #[error("Failed to parse date: {0}")]
62 DateParseError(String),
63
64 #[error("I/O error: {0}")]
66 IoError(#[from] std::io::Error),
67
68 #[error("YAML parsing error: {0}")]
70 YamlError(#[from] SerdeYmlError),
71
72 #[error("JSON parsing error: {0}")]
74 JsonError(#[from] serde_json::Error),
75
76 #[error("TOML parsing error: {0}")]
78 TomlError(#[from] toml::de::Error),
79
80 #[error("Unsupported metadata format: {0}")]
82 UnsupportedFormatError(String),
83
84 #[error("Metadata validation error: {field} - {message}")]
86 ValidationError {
87 field: String,
89 message: String,
91 },
92
93 #[error("UTF-8 decoding error: {0}")]
95 Utf8Error(#[from] std::str::Utf8Error),
96
97 #[error("Unexpected error: {0}")]
99 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
100}
101
102impl MetadataError {
103 pub fn new_extraction_error(message: impl Into<String>) -> Self {
122 Self::ExtractionError {
123 message: message.into(),
124 }
125 }
126
127 pub fn new_processing_error(message: impl Into<String>) -> Self {
146 Self::ProcessingError {
147 message: message.into(),
148 }
149 }
150
151 pub fn new_validation_error(
171 field: impl Into<String>,
172 message: impl Into<String>,
173 ) -> Self {
174 Self::ValidationError {
175 field: field.into(),
176 message: message.into(),
177 }
178 }
179
180 pub fn context<C>(self, ctx: C) -> Self
202 where
203 C: Display + Send + Sync + 'static,
204 {
205 match self {
206 Self::ExtractionError { message } => {
207 Self::ExtractionError {
208 message: format!("{}: {}", ctx, message),
209 }
210 }
211 Self::ProcessingError { message } => {
212 Self::ProcessingError {
213 message: format!("{}: {}", ctx, message),
214 }
215 }
216 Self::MissingFieldError(field) => {
217 Self::MissingFieldError(format!("{}: {}", ctx, field))
218 }
219 Self::DateParseError(error) => {
220 Self::DateParseError(format!("{}: {}", ctx, error))
221 }
222 Self::IoError(error) => Self::IoError(std::io::Error::new(
223 error.kind(),
224 format!("{}: {}", ctx, error),
225 )),
226 Self::YamlError(error) => Self::YamlError(
227 SerdeYmlError::custom(format!("{}: {}", ctx, error)),
228 ),
229 Self::JsonError(error) => {
230 Self::JsonError(serde_json::Error::custom(format!(
231 "{}: {}",
232 ctx, error
233 )))
234 }
235 Self::TomlError(error) => Self::TomlError(
236 toml::de::Error::custom(format!("{}: {}", ctx, error)),
237 ),
238 Self::UnsupportedFormatError(format) => {
239 Self::UnsupportedFormatError(format!(
240 "{}: {}",
241 ctx, format
242 ))
243 }
244 Self::ValidationError { field, message } => {
245 Self::ValidationError {
246 field,
247 message: format!("{}: {}", ctx, message),
248 }
249 }
250 Self::Utf8Error(error) => Self::Utf8Error(error),
251 Self::Other(error) => Self::Other(Box::new(ContextError {
252 context: ctx.to_string(),
253 source: error,
254 })),
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use std::error::Error;
263 use std::fmt;
264 use std::io;
265
266 #[test]
267 fn test_extraction_error() {
268 let error = MetadataError::new_extraction_error(
269 "No valid front matter found.",
270 );
271 assert_eq!(
272 error.to_string(),
273 "Failed to extract metadata: No valid front matter found."
274 );
275 }
276
277 #[test]
278 fn test_processing_error() {
279 let error =
280 MetadataError::new_processing_error("Unknown field");
281 assert_eq!(
282 error.to_string(),
283 "Failed to process metadata: Unknown field"
284 );
285 }
286
287 #[test]
288 fn test_missing_field_error() {
289 let error =
290 MetadataError::MissingFieldError("author".to_string());
291 assert_eq!(
292 error.to_string(),
293 "Missing required metadata field: author"
294 );
295 }
296
297 #[test]
298 fn test_date_parse_error() {
299 let error = MetadataError::DateParseError(
300 "Invalid date format".to_string(),
301 );
302 assert_eq!(
303 error.to_string(),
304 "Failed to parse date: Invalid date format"
305 );
306 }
307
308 #[test]
309 fn test_io_error() {
310 let io_error =
311 io::Error::new(io::ErrorKind::NotFound, "File not found");
312 let error: MetadataError = io_error.into();
313 assert_eq!(error.to_string(), "I/O error: File not found");
314 }
315
316 #[test]
317 fn test_yaml_error() {
318 let yaml_error =
319 serde_yml::Error::custom("YAML structure error");
320 let error: MetadataError = yaml_error.into();
321 assert!(error.to_string().contains("YAML parsing error"));
322 }
323
324 #[test]
325 fn test_json_error() {
326 let json_error =
327 serde_json::Error::custom("Invalid JSON format");
328 let error: MetadataError = json_error.into();
329 assert_eq!(
330 error.to_string(),
331 "JSON parsing error: Invalid JSON format"
332 );
333 }
334
335 #[test]
336 fn test_toml_error() {
337 let toml_error =
338 toml::de::Error::custom("Invalid TOML structure");
339 let error: MetadataError = toml_error.into();
340 assert!(error.to_string().contains("TOML parsing error"));
341 }
342
343 #[test]
344 fn test_unsupported_format_error() {
345 let error =
346 MetadataError::UnsupportedFormatError("XML".to_string());
347 assert_eq!(
348 error.to_string(),
349 "Unsupported metadata format: XML"
350 );
351 }
352
353 #[test]
354 fn test_validation_error() {
355 let error = MetadataError::new_validation_error(
356 "title",
357 "Title must not be empty",
358 );
359 match error {
360 MetadataError::ValidationError { field, message } => {
361 assert_eq!(field, "title");
362 assert_eq!(message, "Title must not be empty");
363 }
364 _ => panic!("Unexpected error variant"),
365 }
366 }
367
368 #[test]
369 #[allow(invalid_from_utf8)]
370 fn test_utf8_error() {
371 let invalid_bytes: &[u8] = &[0xFF, 0xFF];
372 let utf8_error =
373 std::str::from_utf8(invalid_bytes).unwrap_err();
374 let error: MetadataError = utf8_error.into();
375 assert!(matches!(error, MetadataError::Utf8Error(..)));
376 assert!(error.to_string().starts_with("UTF-8 decoding error:"));
377 }
378
379 #[test]
380 fn test_other_error() {
381 use std::error::Error;
382
383 #[derive(Debug)]
384 struct CustomError;
385
386 impl std::fmt::Display for CustomError {
387 fn fmt(
388 &self,
389 f: &mut std::fmt::Formatter<'_>,
390 ) -> std::fmt::Result {
391 write!(f, "Custom error occurred")
392 }
393 }
394
395 impl Error for CustomError {}
396
397 let custom_error = CustomError;
398 let error = MetadataError::Other(Box::new(custom_error));
399
400 assert!(matches!(error, MetadataError::Other(..)));
401 assert_eq!(
402 error.to_string(),
403 "Unexpected error: Custom error occurred"
404 );
405 }
406
407 #[test]
408 fn test_extraction_error_with_empty_message() {
409 let error = MetadataError::new_extraction_error("");
410 assert_eq!(error.to_string(), "Failed to extract metadata: ");
411 }
412
413 #[test]
414 fn test_processing_error_with_empty_message() {
415 let error = MetadataError::new_processing_error("");
416 assert_eq!(error.to_string(), "Failed to process metadata: ");
417 }
418
419 #[test]
420 fn test_validation_error_with_empty_field_and_message() {
421 let error = MetadataError::new_validation_error("", "");
422 match error {
423 MetadataError::ValidationError { field, message } => {
424 assert_eq!(field, "");
425 assert_eq!(message, "");
426 }
427 _ => panic!("Unexpected error variant"),
428 }
429 }
430
431 #[test]
432 fn test_unsupported_format_error_with_empty_format() {
433 let error =
434 MetadataError::UnsupportedFormatError("".to_string());
435 assert_eq!(error.to_string(), "Unsupported metadata format: ");
436 }
437
438 #[test]
439 fn test_yaml_error_with_custom_message() {
440 let yaml_error =
442 serde_yml::Error::custom("Custom YAML error occurred");
443 let error: MetadataError = yaml_error.into();
444 assert!(error.to_string().contains(
445 "YAML parsing error: Custom YAML error occurred"
446 ));
447 }
448
449 #[test]
450 fn test_json_error_with_custom_message() {
451 let json_error = serde_json::Error::custom("Custom JSON error");
453 let error: MetadataError = json_error.into();
454 assert_eq!(
455 error.to_string(),
456 "JSON parsing error: Custom JSON error"
457 );
458 }
459
460 #[test]
461 fn test_toml_error_with_custom_message() {
462 let toml_error = toml::de::Error::custom("Custom TOML error");
464 let error: MetadataError = toml_error.into();
465 assert!(error
466 .to_string()
467 .contains("TOML parsing error: Custom TOML error"));
468 }
469
470 #[test]
471 #[allow(invalid_from_utf8)]
472 fn test_utf8_error_with_specific_invalid_bytes() {
473 let invalid_bytes: &[u8] = &[0xC0, 0x80]; let utf8_error =
475 std::str::from_utf8(invalid_bytes).unwrap_err();
476 let error: MetadataError = utf8_error.into();
477 assert!(matches!(error, MetadataError::Utf8Error(..)));
478 assert!(error.to_string().starts_with("UTF-8 decoding error:"));
479 }
480
481 #[test]
482 fn test_io_error_with_custom_message() {
483 let io_error = std::io::Error::new(
484 std::io::ErrorKind::PermissionDenied,
485 "Permission denied",
486 );
487 let error: MetadataError = io_error.into();
488 assert_eq!(error.to_string(), "I/O error: Permission denied");
489 }
490
491 #[test]
492 fn test_extraction_error_to_debug() {
493 let error = MetadataError::new_extraction_error(
494 "Failed to extract metadata",
495 );
496 assert_eq!(
497 format!("{:?}", error),
498 r#"ExtractionError { message: "Failed to extract metadata" }"#
499 );
500 }
501
502 #[test]
503 fn test_processing_error_to_debug() {
504 let error =
505 MetadataError::new_processing_error("Processing failed");
506 assert_eq!(
507 format!("{:?}", error),
508 r#"ProcessingError { message: "Processing failed" }"#
509 );
510 }
511
512 #[test]
513 fn test_validation_error_to_debug() {
514 let error = MetadataError::new_validation_error(
515 "title",
516 "Title cannot be empty",
517 );
518 assert_eq!(
519 format!("{:?}", error),
520 r#"ValidationError { field: "title", message: "Title cannot be empty" }"#
521 );
522 }
523
524 #[test]
525 fn test_other_error_to_debug() {
526 #[derive(Debug)]
527 struct CustomError;
528
529 impl std::fmt::Display for CustomError {
530 fn fmt(
531 &self,
532 f: &mut std::fmt::Formatter<'_>,
533 ) -> std::fmt::Result {
534 write!(f, "A custom error occurred")
535 }
536 }
537
538 impl std::error::Error for CustomError {}
539
540 let custom_error = CustomError;
541 let error = MetadataError::Other(Box::new(custom_error));
542
543 assert!(format!("{:?}", error).contains("Other("));
545 }
546
547 #[test]
548 fn test_context_error() {
549 let error =
550 MetadataError::new_extraction_error("Failed to parse YAML")
551 .context("Processing file 'example.md'");
552 assert_eq!(
553 error.to_string(),
554 "Failed to extract metadata: Processing file 'example.md': Failed to parse YAML"
555 );
556 }
557
558 #[test]
559 fn test_nested_context_error() {
560 let error =
561 MetadataError::new_extraction_error("Failed to parse YAML")
562 .context("Processing file 'example.md'")
563 .context("Metadata extraction process");
564 assert_eq!(
565 error.to_string(),
566 "Failed to extract metadata: Metadata extraction process: Processing file 'example.md': Failed to parse YAML"
567 );
568 }
569
570 #[test]
571 fn test_extraction_error_empty_message() {
572 let error = MetadataError::ExtractionError {
573 message: "".to_string(),
574 };
575 assert_eq!(error.to_string(), "Failed to extract metadata: ");
576 }
577
578 #[test]
579 fn test_processing_error_empty_message() {
580 let error = MetadataError::ProcessingError {
581 message: "".to_string(),
582 };
583 assert_eq!(error.to_string(), "Failed to process metadata: ");
584 }
585
586 #[test]
587 fn test_missing_field_error_empty_message() {
588 let error = MetadataError::MissingFieldError("".to_string());
589 assert_eq!(
590 error.to_string(),
591 "Missing required metadata field: "
592 );
593 }
594
595 #[test]
596 fn test_date_parse_error_empty_message() {
597 let error = MetadataError::DateParseError("".to_string());
598 assert_eq!(error.to_string(), "Failed to parse date: ");
599 }
600
601 #[test]
602 fn test_extraction_error_debug() {
603 let error = MetadataError::ExtractionError {
604 message: "Error extracting metadata".to_string(),
605 };
606 assert_eq!(
608 format!("{:?}", error),
609 r#"ExtractionError { message: "Error extracting metadata" }"#
610 );
611 }
612
613 #[test]
614 fn test_processing_error_debug() {
615 let error = MetadataError::ProcessingError {
616 message: "Error processing metadata".to_string(),
617 };
618 assert_eq!(
620 format!("{:?}", error),
621 r#"ProcessingError { message: "Error processing metadata" }"#
622 );
623 }
624
625 #[test]
626 fn test_io_error_propagation() {
627 let io_error =
628 io::Error::new(io::ErrorKind::NotFound, "file not found");
629 let error: MetadataError = io_error.into();
630 assert_eq!(error.to_string(), "I/O error: file not found");
631 assert!(matches!(error, MetadataError::IoError(_)));
632 }
633
634 #[test]
635 fn test_yaml_error_propagation() {
636 let yaml_error = serde_yml::Error::custom("Custom YAML error");
637 let error: MetadataError = yaml_error.into();
638 assert_eq!(
639 error.to_string(),
640 "YAML parsing error: Custom YAML error"
641 );
642 assert!(matches!(error, MetadataError::YamlError(_)));
643 }
644
645 #[test]
646 fn test_json_error_propagation() {
647 let json_error = serde_json::Error::custom("Custom JSON error");
648 let error: MetadataError = json_error.into();
649 assert_eq!(
650 error.to_string(),
651 "JSON parsing error: Custom JSON error"
652 );
653 assert!(matches!(error, MetadataError::JsonError(_)));
654 }
655
656 #[test]
657 fn test_toml_error_propagation() {
658 let toml_error = toml::de::Error::custom("Custom TOML error");
659 let error: MetadataError = toml_error.into();
660 assert_eq!(
661 error.to_string(),
662 "TOML parsing error: Custom TOML error\n"
663 );
664 assert!(matches!(error, MetadataError::TomlError(_)));
665 }
666
667 #[test]
668 fn test_missing_field_error_debug() {
669 let error =
670 MetadataError::MissingFieldError("title".to_string());
671 assert_eq!(
672 format!("{:?}", error),
673 r#"MissingFieldError("title")"#
674 );
675 }
676
677 #[test]
678 fn test_date_parse_error_debug() {
679 let error = MetadataError::DateParseError(
680 "Invalid date format".to_string(),
681 );
682 assert_eq!(
683 format!("{:?}", error),
684 r#"DateParseError("Invalid date format")"#
685 );
686 }
687
688 #[test]
689 fn test_empty_yaml_error_message() {
690 let yaml_error = serde_yml::Error::custom("");
691 let error: MetadataError = yaml_error.into();
692 assert_eq!(error.to_string(), "YAML parsing error: ");
693 }
694
695 #[test]
696 fn test_empty_json_error_message() {
697 let json_error = serde_json::Error::custom("");
698 let error: MetadataError = json_error.into();
699 assert_eq!(error.to_string(), "JSON parsing error: ");
700 }
701
702 #[test]
703 fn test_empty_toml_error_message() {
704 let toml_error = toml::de::Error::custom("");
705 let error: MetadataError = toml_error.into();
706 assert_eq!(error.to_string(), "TOML parsing error: \n");
707 }
708
709 #[derive(Debug)]
711 struct CustomError;
712
713 impl fmt::Display for CustomError {
714 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
715 write!(f, "Custom error occurred")
716 }
717 }
718
719 impl Error for CustomError {}
720
721 #[test]
722 fn test_context_error_fmt() {
723 let custom_error = CustomError;
724 let context_error = ContextError {
725 context: "An error occurred while processing".to_string(),
726 source: Box::new(custom_error),
727 };
728
729 let formatted = format!("{}", context_error);
730 assert_eq!(
731 formatted,
732 "An error occurred while processing: Custom error occurred"
733 );
734 }
735
736 #[test]
737 fn test_context_error_source() {
738 let custom_error = CustomError;
739 let context_error = ContextError {
740 context: "Error with context".to_string(),
741 source: Box::new(custom_error),
742 };
743
744 let source = context_error.source().unwrap();
746 assert_eq!(source.to_string(), "Custom error occurred");
747 }
748
749 #[test]
750 fn test_context_error_debug() {
751 let custom_error = CustomError;
752 let context_error = ContextError {
753 context: "Error during processing".to_string(),
754 source: Box::new(custom_error),
755 };
756
757 let debug_output = format!("{:?}", context_error);
758
759 assert!(debug_output.contains("ContextError"));
761 assert!(debug_output.contains("Error during processing"));
762 assert!(debug_output.contains("CustomError"));
763 }
764}