1use noyalib::Error as SerdeYmlError;
7use serde::de::Error as SerdeError;
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 = noyalib::Error::custom("YAML structure error");
319 let error: MetadataError = yaml_error.into();
320 assert!(error.to_string().contains("YAML parsing error"));
321 }
322
323 #[test]
324 fn test_json_error() {
325 let json_error =
326 serde_json::Error::custom("Invalid JSON format");
327 let error: MetadataError = json_error.into();
328 assert_eq!(
329 error.to_string(),
330 "JSON parsing error: Invalid JSON format"
331 );
332 }
333
334 #[test]
335 fn test_toml_error() {
336 let toml_error =
337 toml::de::Error::custom("Invalid TOML structure");
338 let error: MetadataError = toml_error.into();
339 assert!(error.to_string().contains("TOML parsing error"));
340 }
341
342 #[test]
343 fn test_unsupported_format_error() {
344 let error =
345 MetadataError::UnsupportedFormatError("XML".to_string());
346 assert_eq!(
347 error.to_string(),
348 "Unsupported metadata format: XML"
349 );
350 }
351
352 #[test]
353 fn test_validation_error() {
354 let error = MetadataError::new_validation_error(
355 "title",
356 "Title must not be empty",
357 );
358 match error {
359 MetadataError::ValidationError { field, message } => {
360 assert_eq!(field, "title");
361 assert_eq!(message, "Title must not be empty");
362 }
363 _ => panic!("Unexpected error variant"),
364 }
365 }
366
367 #[test]
368 #[allow(invalid_from_utf8)]
369 fn test_utf8_error() {
370 let invalid_bytes: &[u8] = &[0xFF, 0xFF];
371 let utf8_error =
372 std::str::from_utf8(invalid_bytes).unwrap_err();
373 let error: MetadataError = utf8_error.into();
374 assert!(matches!(error, MetadataError::Utf8Error(..)));
375 assert!(error.to_string().starts_with("UTF-8 decoding error:"));
376 }
377
378 #[test]
379 fn test_other_error() {
380 use std::error::Error;
381
382 #[derive(Debug)]
383 struct CustomError;
384
385 impl std::fmt::Display for CustomError {
386 fn fmt(
387 &self,
388 f: &mut std::fmt::Formatter<'_>,
389 ) -> std::fmt::Result {
390 write!(f, "Custom error occurred")
391 }
392 }
393
394 impl Error for CustomError {}
395
396 let custom_error = CustomError;
397 let error = MetadataError::Other(Box::new(custom_error));
398
399 assert!(matches!(error, MetadataError::Other(..)));
400 assert_eq!(
401 error.to_string(),
402 "Unexpected error: Custom error occurred"
403 );
404 }
405
406 #[test]
407 fn test_extraction_error_with_empty_message() {
408 let error = MetadataError::new_extraction_error("");
409 assert_eq!(error.to_string(), "Failed to extract metadata: ");
410 }
411
412 #[test]
413 fn test_processing_error_with_empty_message() {
414 let error = MetadataError::new_processing_error("");
415 assert_eq!(error.to_string(), "Failed to process metadata: ");
416 }
417
418 #[test]
419 fn test_validation_error_with_empty_field_and_message() {
420 let error = MetadataError::new_validation_error("", "");
421 match error {
422 MetadataError::ValidationError { field, message } => {
423 assert_eq!(field, "");
424 assert_eq!(message, "");
425 }
426 _ => panic!("Unexpected error variant"),
427 }
428 }
429
430 #[test]
431 fn test_unsupported_format_error_with_empty_format() {
432 let error =
433 MetadataError::UnsupportedFormatError("".to_string());
434 assert_eq!(error.to_string(), "Unsupported metadata format: ");
435 }
436
437 #[test]
438 fn test_yaml_error_with_custom_message() {
439 let yaml_error =
441 noyalib::Error::custom("Custom YAML error occurred");
442 let error: MetadataError = yaml_error.into();
443 assert!(error.to_string().contains(
444 "YAML parsing error: Custom YAML error occurred"
445 ));
446 }
447
448 #[test]
449 fn test_json_error_with_custom_message() {
450 let json_error = serde_json::Error::custom("Custom JSON error");
452 let error: MetadataError = json_error.into();
453 assert_eq!(
454 error.to_string(),
455 "JSON parsing error: Custom JSON error"
456 );
457 }
458
459 #[test]
460 fn test_toml_error_with_custom_message() {
461 let toml_error = toml::de::Error::custom("Custom TOML error");
463 let error: MetadataError = toml_error.into();
464 assert!(error
465 .to_string()
466 .contains("TOML parsing error: Custom TOML error"));
467 }
468
469 #[test]
470 #[allow(invalid_from_utf8)]
471 fn test_utf8_error_with_specific_invalid_bytes() {
472 let invalid_bytes: &[u8] = &[0xC0, 0x80]; let utf8_error =
474 std::str::from_utf8(invalid_bytes).unwrap_err();
475 let error: MetadataError = utf8_error.into();
476 assert!(matches!(error, MetadataError::Utf8Error(..)));
477 assert!(error.to_string().starts_with("UTF-8 decoding error:"));
478 }
479
480 #[test]
481 fn test_io_error_with_custom_message() {
482 let io_error = std::io::Error::new(
483 std::io::ErrorKind::PermissionDenied,
484 "Permission denied",
485 );
486 let error: MetadataError = io_error.into();
487 assert_eq!(error.to_string(), "I/O error: Permission denied");
488 }
489
490 #[test]
491 fn test_extraction_error_to_debug() {
492 let error = MetadataError::new_extraction_error(
493 "Failed to extract metadata",
494 );
495 assert_eq!(
496 format!("{:?}", error),
497 r#"ExtractionError { message: "Failed to extract metadata" }"#
498 );
499 }
500
501 #[test]
502 fn test_processing_error_to_debug() {
503 let error =
504 MetadataError::new_processing_error("Processing failed");
505 assert_eq!(
506 format!("{:?}", error),
507 r#"ProcessingError { message: "Processing failed" }"#
508 );
509 }
510
511 #[test]
512 fn test_validation_error_to_debug() {
513 let error = MetadataError::new_validation_error(
514 "title",
515 "Title cannot be empty",
516 );
517 assert_eq!(
518 format!("{:?}", error),
519 r#"ValidationError { field: "title", message: "Title cannot be empty" }"#
520 );
521 }
522
523 #[test]
524 fn test_other_error_to_debug() {
525 #[derive(Debug)]
526 struct CustomError;
527
528 impl std::fmt::Display for CustomError {
529 fn fmt(
530 &self,
531 f: &mut std::fmt::Formatter<'_>,
532 ) -> std::fmt::Result {
533 write!(f, "A custom error occurred")
534 }
535 }
536
537 impl std::error::Error for CustomError {}
538
539 let custom_error = CustomError;
540 let error = MetadataError::Other(Box::new(custom_error));
541
542 assert!(format!("{:?}", error).contains("Other("));
544 }
545
546 #[test]
547 fn test_context_error() {
548 let error =
549 MetadataError::new_extraction_error("Failed to parse YAML")
550 .context("Processing file 'example.md'");
551 assert_eq!(
552 error.to_string(),
553 "Failed to extract metadata: Processing file 'example.md': Failed to parse YAML"
554 );
555 }
556
557 #[test]
558 fn test_nested_context_error() {
559 let error =
560 MetadataError::new_extraction_error("Failed to parse YAML")
561 .context("Processing file 'example.md'")
562 .context("Metadata extraction process");
563 assert_eq!(
564 error.to_string(),
565 "Failed to extract metadata: Metadata extraction process: Processing file 'example.md': Failed to parse YAML"
566 );
567 }
568
569 #[test]
570 fn test_extraction_error_empty_message() {
571 let error = MetadataError::ExtractionError {
572 message: "".to_string(),
573 };
574 assert_eq!(error.to_string(), "Failed to extract metadata: ");
575 }
576
577 #[test]
578 fn test_processing_error_empty_message() {
579 let error = MetadataError::ProcessingError {
580 message: "".to_string(),
581 };
582 assert_eq!(error.to_string(), "Failed to process metadata: ");
583 }
584
585 #[test]
586 fn test_missing_field_error_empty_message() {
587 let error = MetadataError::MissingFieldError("".to_string());
588 assert_eq!(
589 error.to_string(),
590 "Missing required metadata field: "
591 );
592 }
593
594 #[test]
595 fn test_date_parse_error_empty_message() {
596 let error = MetadataError::DateParseError("".to_string());
597 assert_eq!(error.to_string(), "Failed to parse date: ");
598 }
599
600 #[test]
601 fn test_extraction_error_debug() {
602 let error = MetadataError::ExtractionError {
603 message: "Error extracting metadata".to_string(),
604 };
605 assert_eq!(
607 format!("{:?}", error),
608 r#"ExtractionError { message: "Error extracting metadata" }"#
609 );
610 }
611
612 #[test]
613 fn test_processing_error_debug() {
614 let error = MetadataError::ProcessingError {
615 message: "Error processing metadata".to_string(),
616 };
617 assert_eq!(
619 format!("{:?}", error),
620 r#"ProcessingError { message: "Error processing metadata" }"#
621 );
622 }
623
624 #[test]
625 fn test_io_error_propagation() {
626 let io_error =
627 io::Error::new(io::ErrorKind::NotFound, "file not found");
628 let error: MetadataError = io_error.into();
629 assert_eq!(error.to_string(), "I/O error: file not found");
630 assert!(matches!(error, MetadataError::IoError(_)));
631 }
632
633 #[test]
634 fn test_yaml_error_propagation() {
635 let yaml_error = noyalib::Error::custom("Custom YAML error");
636 let error: MetadataError = yaml_error.into();
637 assert_eq!(
638 error.to_string(),
639 "YAML parsing error: Custom YAML error"
640 );
641 assert!(matches!(error, MetadataError::YamlError(_)));
642 }
643
644 #[test]
645 fn test_json_error_propagation() {
646 let json_error = serde_json::Error::custom("Custom JSON error");
647 let error: MetadataError = json_error.into();
648 assert_eq!(
649 error.to_string(),
650 "JSON parsing error: Custom JSON error"
651 );
652 assert!(matches!(error, MetadataError::JsonError(_)));
653 }
654
655 #[test]
656 fn test_toml_error_propagation() {
657 let toml_error = toml::de::Error::custom("Custom TOML error");
658 let error: MetadataError = toml_error.into();
659 assert_eq!(
660 error.to_string(),
661 "TOML parsing error: Custom TOML error\n"
662 );
663 assert!(matches!(error, MetadataError::TomlError(_)));
664 }
665
666 #[test]
667 fn test_missing_field_error_debug() {
668 let error =
669 MetadataError::MissingFieldError("title".to_string());
670 assert_eq!(
671 format!("{:?}", error),
672 r#"MissingFieldError("title")"#
673 );
674 }
675
676 #[test]
677 fn test_date_parse_error_debug() {
678 let error = MetadataError::DateParseError(
679 "Invalid date format".to_string(),
680 );
681 assert_eq!(
682 format!("{:?}", error),
683 r#"DateParseError("Invalid date format")"#
684 );
685 }
686
687 #[test]
688 fn test_empty_yaml_error_message() {
689 let yaml_error = noyalib::Error::custom("");
690 let error: MetadataError = yaml_error.into();
691 assert_eq!(error.to_string(), "YAML parsing error: ");
692 }
693
694 #[test]
695 fn test_empty_json_error_message() {
696 let json_error = serde_json::Error::custom("");
697 let error: MetadataError = json_error.into();
698 assert_eq!(error.to_string(), "JSON parsing error: ");
699 }
700
701 #[test]
702 fn test_empty_toml_error_message() {
703 let toml_error = toml::de::Error::custom("");
704 let error: MetadataError = toml_error.into();
705 assert_eq!(error.to_string(), "TOML parsing error: \n");
706 }
707
708 #[derive(Debug)]
710 struct CustomError;
711
712 impl fmt::Display for CustomError {
713 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714 write!(f, "Custom error occurred")
715 }
716 }
717
718 impl Error for CustomError {}
719
720 #[test]
721 fn test_context_error_fmt() {
722 let custom_error = CustomError;
723 let context_error = ContextError {
724 context: "An error occurred while processing".to_string(),
725 source: Box::new(custom_error),
726 };
727
728 let formatted = format!("{}", context_error);
729 assert_eq!(
730 formatted,
731 "An error occurred while processing: Custom error occurred"
732 );
733 }
734
735 #[test]
736 fn test_context_error_source() {
737 let custom_error = CustomError;
738 let context_error = ContextError {
739 context: "Error with context".to_string(),
740 source: Box::new(custom_error),
741 };
742
743 let source = context_error.source().unwrap();
745 assert_eq!(source.to_string(), "Custom error occurred");
746 }
747
748 #[test]
749 fn test_context_error_debug() {
750 let custom_error = CustomError;
751 let context_error = ContextError {
752 context: "Error during processing".to_string(),
753 source: Box::new(custom_error),
754 };
755
756 let debug_output = format!("{:?}", context_error);
757
758 assert!(debug_output.contains("ContextError"));
760 assert!(debug_output.contains("Error during processing"));
761 assert!(debug_output.contains("CustomError"));
762 }
763}