1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use std::{fmt, io, path::PathBuf};
/// Unified result type for dotling operations.
pub type Result<T> = std::result::Result<T, Error>;
/// All errors that dotling can produce.
///
/// Each variant carries enough context to produce a helpful, user-facing
/// message without the caller needing to guess what went wrong.
#[derive(Debug)]
pub enum Error {
/// An I/O error with path and operation context.
Io {
path: PathBuf,
operation: &'static str,
source: io::Error,
},
/// A configuration file parsing or validation error.
Config {
message: String,
line: Option<usize>,
},
/// A cryptographic operation failed.
Crypto(String),
/// A deployment operation failed.
Deploy { entry: String, message: String },
/// A vault operation failed.
Vault(String),
/// A template rendering or validation error.
Template { source: String, message: String },
/// A user-facing error with a clear message (no internal detail needed).
User(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io {
path,
operation,
source,
} => {
write!(f, "{operation} `{}`: {source}", path.display())
}
Self::Config {
message,
line: Some(n),
} => {
write!(f, "config error (line {n}): {message}")
}
Self::Config {
message,
line: None,
} => {
write!(f, "config error: {message}")
}
Self::Crypto(msg) => write!(f, "crypto error: {msg}"),
Self::Deploy { entry, message } => {
write!(f, "deploy `{entry}`: {message}")
}
Self::Vault(msg) => write!(f, "vault error: {msg}"),
Self::Template { source, message } => {
write!(f, "template error in `{source}`: {message}")
}
Self::User(msg) => write!(f, "{msg}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io { source, .. } => Some(source),
_ => None,
}
}
}
impl Error {
/// Convenience constructor for I/O errors with context.
pub fn io(path: impl Into<PathBuf>, operation: &'static str, source: io::Error) -> Self {
Self::Io {
path: path.into(),
operation,
source,
}
}
}