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 #[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}