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