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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Error type for the include layer.
use std::error::Error as StdError;
use std::fmt;
use std::io;
use std::path::PathBuf;
/// Result alias used throughout `cordis-include`.
pub type Result<T> = std::result::Result<T, IncludeError>;
/// Errors produced while reading entry trees and loader files.
#[derive(Debug)]
pub enum IncludeError {
/// An underlying filesystem operation failed.
Io(io::Error),
/// A config file could not be parsed or serialized.
Parse {
/// Which format was being processed (`"yaml"` or `"json"`).
format: &'static str,
/// The parser or serializer error.
source: Box<dyn StdError + Send + Sync>,
},
/// The file extension does not map to a supported format.
UnknownFormat {
/// The path that was opened.
path: PathBuf,
},
/// The target file is read-only, so configuration cannot be written back.
ReadOnly {
/// The path that was opened.
path: PathBuf,
},
/// Two entries in one tree declare the same id.
DuplicateId {
/// The conflicting id.
id: String,
},
/// No entry with the requested id exists in the tree.
EntryNotFound {
/// The requested (possibly composite) id.
id: String,
},
/// An entry id is empty or contains the `:` path separator.
InvalidId {
/// The offending id.
id: String,
},
/// An entry has no plugin name.
InvalidName,
/// A `${{ env.NAME }}` template referenced an unset variable.
MissingEnv {
/// The full expression that failed, e.g. `env.MISSING`.
expression: String,
},
/// A `${{ ... }}` template used an unsupported expression.
UnknownExpression {
/// The unsupported expression.
expression: String,
},
/// A `${{` template was never closed with `}}`.
Unterminated {
/// The input containing the dangling `${{`.
input: String,
},
/// The referenced entry does not belong to this tree.
NotInTree,
/// The requested move would place an entry inside its own subtree.
Cycle,
/// Setting up or running a file watcher failed (`watch` feature).
Watch {
/// The underlying watcher error.
source: Box<dyn StdError + Send + Sync>,
},
/// Free-form message from layers composing multiple files (import
/// cycles, unreadable sub-files).
Message {
/// The message.
message: String,
},
}
impl fmt::Display for IncludeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => write!(f, "filesystem error: {error}"),
Self::Parse { format, source } => write!(f, "{format} parse error: {source}"),
Self::UnknownFormat { path } => write!(
f,
"unsupported config format (expected .yml, .yaml, or .json): {}",
path.display()
),
Self::ReadOnly { path } => {
write!(f, "config file is read-only: {}", path.display())
}
Self::DuplicateId { id } => write!(f, "duplicate entry id `{id}`"),
Self::EntryNotFound { id } => write!(f, "entry `{id}` not found"),
Self::InvalidId { id } => {
write!(
f,
"invalid entry id `{id}`: must be non-empty and contain no `:`"
)
}
Self::InvalidName => write!(f, "entry name must be non-empty"),
Self::MissingEnv { expression } => {
write!(f, "environment variable unset: `{expression}`")
}
Self::UnknownExpression { expression } => {
write!(f, "unsupported template expression `{expression}`")
}
Self::Unterminated { input } => {
write!(f, "unterminated template `${{{{`}} in `{input}`")
}
Self::NotInTree => write!(f, "entry does not belong to this tree"),
Self::Cycle => write!(f, "cannot move an entry into its own subtree"),
Self::Watch { source } => write!(f, "file watcher error: {source}"),
Self::Message { message } => write!(f, "{message}"),
}
}
}
impl StdError for IncludeError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::Io(error) => Some(error),
Self::Parse { source, .. } => Some(source.as_ref()),
Self::Watch { source } => Some(source.as_ref()),
_ => None,
}
}
}
impl From<io::Error> for IncludeError {
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}