use crate::metadata::FrontMatterFormat;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use core::fmt::Display;
use core::ops::Range;
#[cfg(feature = "yaml")]
use noyalib::Error as SerdeYmlError;
#[cfg(any(feature = "yaml", feature = "toml", feature = "json"))]
use serde::de::Error as SerdeError;
use thiserror::Error;
pub type BoxedError =
Box<dyn core::error::Error + Send + Sync + 'static>;
#[derive(Debug)]
pub struct ContextError {
context: String,
source: BoxedError,
}
impl core::fmt::Display for ContextError {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
write!(f, "{}: {}", self.context, self.source)
}
}
impl core::error::Error for ContextError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&*self.source)
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum MetadataError {
#[error("{format} front matter failed to parse{}: {source}", span_suffix(.span))]
Parse {
format: FrontMatterFormat,
span: Option<Range<usize>>,
#[source]
source: BoxedError,
},
#[error("Failed to extract metadata: {message}")]
ExtractionError {
message: String,
},
#[error("Failed to process metadata: {message}")]
ProcessingError {
message: String,
},
#[error("Missing required metadata field: {0}")]
MissingFieldError(String),
#[error("Failed to parse date: {0}")]
DateParseError(String),
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
#[error("YAML parsing error: {0}")]
YamlError(#[from] SerdeYmlError),
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
#[error("JSON parsing error: {0}")]
JsonError(#[from] serde_json::Error),
#[cfg(feature = "toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
#[error("TOML parsing error: {0}")]
TomlError(#[from] toml::de::Error),
#[error("Unsupported metadata format: {0}")]
UnsupportedFormatError(String),
#[error("Metadata validation error: {field} - {message}")]
ValidationError {
field: String,
message: String,
},
#[deprecated(
since = "0.0.8",
note = "never produced by the crate; match on `IoError` for invalid UTF-8 from a reader"
)]
#[error("UTF-8 decoding error: {0}")]
Utf8Error(#[from] core::str::Utf8Error),
#[error("Unexpected error: {0}")]
Other(BoxedError),
}
fn span_suffix(span: &Option<Range<usize>>) -> String {
match span {
Some(r) => format!(" at bytes {}..{}", r.start, r.end),
None => String::new(),
}
}
impl MetadataError {
pub fn other<E>(error: E) -> Self
where
E: core::error::Error + Send + Sync + 'static,
{
Self::Other(Box::new(error))
}
#[cfg(any(feature = "yaml", feature = "toml", feature = "json"))]
pub(crate) fn parse<E>(
format: FrontMatterFormat,
span: Option<Range<usize>>,
source: E,
) -> Self
where
E: core::error::Error + Send + Sync + 'static,
{
Self::Parse {
format,
span,
source: Box::new(source),
}
}
pub fn new_extraction_error(message: impl Into<String>) -> Self {
Self::ExtractionError {
message: message.into(),
}
}
pub fn new_processing_error(message: impl Into<String>) -> Self {
Self::ProcessingError {
message: message.into(),
}
}
pub fn new_validation_error(
field: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::ValidationError {
field: field.into(),
message: message.into(),
}
}
pub fn context<C>(self, ctx: C) -> Self
where
C: Display + Send + Sync + 'static,
{
match self {
#[cfg(feature = "std")]
Self::IoError(error) => Self::IoError(std::io::Error::new(
error.kind(),
format!("{}: {}", ctx, error),
)),
#[cfg(feature = "yaml")]
Self::YamlError(error) => Self::YamlError(
SerdeYmlError::custom(format!("{}: {}", ctx, error)),
),
#[cfg(feature = "json")]
Self::JsonError(error) => {
Self::JsonError(serde_json::Error::custom(format!(
"{}: {}",
ctx, error
)))
}
#[cfg(feature = "toml")]
Self::TomlError(error) => Self::TomlError(
toml::de::Error::custom(format!("{}: {}", ctx, error)),
),
Self::Parse {
format,
span,
source,
} => Self::Parse {
format,
span,
source: Box::new(ContextError {
context: ctx.to_string(),
source,
}),
},
Self::Other(error) => Self::Other(Box::new(ContextError {
context: ctx.to_string(),
source: error,
})),
other => other.prefix_message(&ctx),
}
}
fn prefix_message<C: Display>(self, ctx: &C) -> Self {
match self {
Self::ExtractionError { message } => {
Self::ExtractionError {
message: format!("{}: {}", ctx, message),
}
}
Self::ProcessingError { message } => {
Self::ProcessingError {
message: format!("{}: {}", ctx, message),
}
}
Self::MissingFieldError(field) => {
Self::MissingFieldError(format!("{}: {}", ctx, field))
}
Self::DateParseError(error) => {
Self::DateParseError(format!("{}: {}", ctx, error))
}
Self::UnsupportedFormatError(format) => {
Self::UnsupportedFormatError(format!(
"{}: {}",
ctx, format
))
}
Self::ValidationError { field, message } => {
Self::ValidationError {
field,
message: format!("{}: {}", ctx, message),
}
}
other => other,
}
}
}
#[cfg(all(
test,
feature = "std",
feature = "yaml",
feature = "toml",
feature = "json"
))]
mod tests;
#[cfg(all(
test,
feature = "std",
feature = "yaml",
feature = "toml",
feature = "json"
))]
mod context_tests;