Skip to main content

folio_vitae/
error.rs

1use std::{env, num::ParseIntError, path::PathBuf};
2
3use thiserror::Error;
4
5pub type AppResult<T> = Result<T, AppError>;
6
7#[derive(Debug, Error)]
8pub enum AppError {
9    #[error(transparent)]
10    Io(#[from] std::io::Error),
11
12    #[error(transparent)]
13    EnvVar(#[from] env::VarError),
14
15    #[error(transparent)]
16    ParseInt(#[from] ParseIntError),
17
18    #[error(transparent)]
19    Template(#[from] tera::Error),
20
21    #[error(transparent)]
22    SerdeYaml(#[from] serde_yaml::Error),
23
24    #[error(transparent)]
25    SerdeJson(#[from] serde_json::Error),
26
27    #[cfg(feature = "serve")]
28    #[error(transparent)]
29    Image(#[from] image::ImageError),
30
31    #[cfg(feature = "smtp")]
32    #[error(transparent)]
33    Address(#[from] lettre::address::AddressError),
34
35    #[cfg(feature = "smtp")]
36    #[error(transparent)]
37    Lettre(#[from] lettre::error::Error),
38
39    #[cfg(feature = "smtp")]
40    #[error(transparent)]
41    LettreSmtp(#[from] lettre::transport::smtp::Error),
42
43    #[error(transparent)]
44    Dotenv(#[from] dotenvy::Error),
45
46    #[cfg(feature = "serve")]
47    #[error(transparent)]
48    Hyper(#[from] hyper::Error),
49
50    #[cfg(feature = "serve")]
51    #[error(transparent)]
52    Svg(#[from] resvg::usvg::Error),
53
54    #[cfg(feature = "serve")]
55    #[error(transparent)]
56    Grass(#[from] Box<grass::Error>),
57
58    #[error("Unsupported image format for {0:?}")]
59    // Kept for future explicit image format validation.
60    #[allow(dead_code)]
61    UnsupportedImageFormat(PathBuf),
62
63    #[error("{context}: {source}")]
64    Contextual {
65        context: String,
66        #[source]
67        source: Box<AppError>,
68    },
69
70    #[error("{0}")]
71    Message(String),
72}
73
74pub trait ResultExt<T> {
75    fn with_context<C, F>(self, f: F) -> AppResult<T>
76    where
77        F: FnOnce() -> C,
78        C: Into<String>;
79}
80
81impl<T, E> ResultExt<T> for Result<T, E>
82where
83    AppError: From<E>,
84{
85    fn with_context<C, F>(self, f: F) -> AppResult<T>
86    where
87        F: FnOnce() -> C,
88        C: Into<String>,
89    {
90        self.map_err(|error| {
91            let source = AppError::from(error);
92            source.with_context(f())
93        })
94    }
95}
96
97impl AppError {
98    pub fn with_context(self, context: impl Into<String>) -> AppError {
99        AppError::Contextual { context: context.into(), source: Box::new(self) }
100    }
101
102    pub fn msg(message: impl Into<String>) -> AppError {
103        AppError::Message(message.into())
104    }
105}