use std::borrow::Cow;
use camino::{Utf8Path, Utf8PathBuf};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct Error {
kind: Box<ErrorKind>,
path: Option<Utf8PathBuf>,
}
impl Error {
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub fn path(&self) -> Option<&Utf8Path> {
self.path.as_deref()
}
}
impl Error {
#[track_caller]
#[doc(hidden)]
pub fn new(kind: ErrorKind) -> Self {
Self {
kind: Box::new(kind),
path: None,
}
}
#[track_caller]
#[doc(hidden)]
pub fn other(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Error::new(ErrorKind::Other(e.into()))
}
pub(crate) fn with_path(mut self, path: impl Into<Utf8PathBuf>) -> Self {
self.path = Some(path.into());
self
}
}
impl From<std::fmt::Error> for Error {
#[track_caller]
fn from(_: std::fmt::Error) -> Self {
panic!()
}
}
impl From<std::io::Error> for Error {
#[track_caller]
fn from(e: std::io::Error) -> Self {
Self::new(ErrorKind::FileRead(e))
}
}
#[cfg(feature = "read")]
impl From<toml::de::Error> for Error {
#[track_caller]
fn from(e: toml::de::Error) -> Self {
Self::new(ErrorKind::Parse(e))
}
}
#[cfg(feature = "render")]
impl From<image::ImageError> for Error {
#[track_caller]
fn from(e: image::ImageError) -> Self {
Self::new(ErrorKind::Image(e))
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind() {
ErrorKind::FileRead(_) => match self.path() {
Some(path) => write!(f, "failed to read file `{path}`"),
None => write!(f, "failed to read file"),
},
#[cfg(feature = "read")]
ErrorKind::Parse(_) => f.write_str("failed to deserialize file"),
ErrorKind::Incomplete(_) => f.write_str("incomplete meme definition"),
#[cfg(feature = "render")]
ErrorKind::Image(e) => std::fmt::Display::fmt(e, f),
ErrorKind::Other(e) => std::fmt::Display::fmt(e, f),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &*self.kind {
ErrorKind::FileRead(e) => Some(e),
#[cfg(feature = "read")]
ErrorKind::Parse(e) => Some(e),
ErrorKind::Incomplete(_) => None,
#[cfg(feature = "render")]
ErrorKind::Image(e) => Some(e),
ErrorKind::Other(_) => None,
}
}
}
#[non_exhaustive]
#[derive(Debug)]
pub enum ErrorKind {
FileRead(std::io::Error),
#[cfg(feature = "read")]
Parse(toml::de::Error),
Incomplete(Vec<IncompleteMemeDefinition>),
#[cfg(feature = "render")]
Image(image::ImageError),
Other(Box<dyn std::error::Error + Send + Sync>),
}
#[non_exhaustive]
#[derive(Debug)]
pub enum IncompleteMemeDefinition {
MissingField(Vec<Cow<'static, str>>),
MissingParameters(Vec<Cow<'static, str>>),
}
impl IncompleteMemeDefinition {
#[doc(hidden)]
#[cfg(feature = "cli")]
pub fn keys(&self) -> impl IntoIterator<Item = impl AsRef<str>> {
match self {
Self::MissingField(keys) | Self::MissingParameters(keys) => keys,
}
}
}
impl std::fmt::Display for IncompleteMemeDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn write_path(
f: &mut std::fmt::Formatter<'_>,
path: &[Cow<'static, str>],
) -> std::fmt::Result {
for (i, name) in path.iter().enumerate() {
if i > 0 {
write!(f, ".")?;
}
match name {
Cow::Borrowed(static_name) => write!(f, "{static_name}")?,
Cow::Owned(dynamic_name) => write!(f, "{dynamic_name:?}")?,
}
}
Ok(())
}
match self {
Self::MissingField(path) => {
write!(f, "missing field: ")?;
write_path(f, path)?;
Ok(())
}
Self::MissingParameters(path) => {
write!(f, "missing parameters for ")?;
write_path(f, path)?;
Ok(())
}
}
}
}
impl std::error::Error for IncompleteMemeDefinition {}
#[cfg(feature = "cli")]
#[derive(Default, Debug, Clone)]
pub struct FileCache {
files: std::collections::HashMap<Utf8PathBuf, ariadne::Source>,
}
#[cfg(feature = "cli")]
impl ariadne::Cache<&Utf8Path> for FileCache {
type Storage = String;
fn fetch(
&mut self,
path: &&Utf8Path,
) -> std::result::Result<&ariadne::Source, impl std::fmt::Debug> {
use std::collections::hash_map::Entry;
Ok::<_, Error>(match self.files.entry((*path).to_owned()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(ariadne::Source::from(
std::fs::read_to_string(path).with_path(path)?,
)),
})
}
fn display<'a>(&self, path: &'a &Utf8Path) -> Option<impl std::fmt::Display + 'a> {
Some(path)
}
}
pub(crate) trait ResultExt<T, E>
where
Self: Sized,
E: Into<Error>,
{
fn with_path(self, path: impl Into<Utf8PathBuf>) -> Result<T>;
}
impl<T, E> ResultExt<T, E> for std::result::Result<T, E>
where
Self: Sized,
E: Into<Error>,
{
#[track_caller]
fn with_path(self, path: impl Into<Utf8PathBuf>) -> Result<T> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(e.into().with_path(path)),
}
}
}