1use std::io;
6use std::path::PathBuf;
7
8use thiserror::Error;
9
10pub type Result<T, E = Error> = std::result::Result<T, E>;
11
12#[derive(Debug, Error)]
13pub enum Error {
14 #[error("snapshot error: {0}")]
15 Snapshot(#[from] SnapshotError),
16
17 #[error("store error: {0}")]
18 Store(#[from] StoreError),
19
20 #[error("config error: {0}")]
21 Config(#[from] ConfigError),
22
23 #[error("patch error: {0}")]
24 Patch(#[from] PatchError),
25
26 #[error("name error: {0}")]
27 Name(#[from] NameError),
28
29 #[error("i/o error: {0}")]
30 Io(#[from] io::Error),
31}
32
33#[derive(Debug, Error)]
34pub enum NameError {
35 #[error("empty value where {kind} was expected")]
36 Empty { kind: &'static str },
37
38 #[error("{kind} too long: {len} > {max}")]
39 TooLong {
40 kind: &'static str,
41 len: usize,
42 max: usize,
43 },
44
45 #[error("invalid character {ch:?} in {kind}: {value}")]
46 InvalidCharacter {
47 kind: &'static str,
48 ch: char,
49 value: String,
50 },
51
52 #[error("{kind} {value:?} does not match required pattern {pattern}")]
53 PatternMismatch {
54 kind: &'static str,
55 value: String,
56 pattern: &'static str,
57 },
58
59 #[error("invalid commit sha {value:?}: must be exactly 40 lowercase hex characters")]
60 InvalidCommitSha { value: String },
61
62 #[error("commit sha prefix is empty")]
63 EmptyCommitShaPrefix,
64
65 #[error("invalid commit sha prefix {value:?}: too short ({len} chars; needs 7 to 40)")]
66 CommitShaPrefixTooShort { value: String, len: usize },
67
68 #[error("invalid commit sha prefix {value:?}: too long ({len} chars; needs 7 to 40)")]
69 CommitShaPrefixTooLong { value: String, len: usize },
70
71 #[error("invalid commit sha prefix {value:?}: non-hex character {ch:?} at position {position}")]
72 CommitShaPrefixNonHex {
73 value: String,
74 ch: char,
75 position: usize,
76 },
77
78 #[error("invalid arity: {raw:?} could not be parsed as an integer in 0..=255")]
79 InvalidArityParse { raw: String },
80
81 #[error("invalid arity {value}: must be 0..=255")]
82 InvalidArity { value: i64 },
83
84 #[error("invalid mfa {value:?}: expected 'mod:fun/arity'")]
85 InvalidMfa { value: String },
86
87 #[error("invalid {kind} {value:?}: {reason}")]
88 Invalid {
89 kind: &'static str,
90 value: String,
91 reason: &'static str,
92 },
93}
94
95#[derive(Debug, Error)]
96pub enum SnapshotError {
97 #[error("malformed header at line {line}: {detail}")]
98 MalformedHeader { line: usize, detail: String },
99
100 #[error("unknown header key {key:?} at line {line}")]
101 UnknownHeaderKey { line: usize, key: String },
102
103 #[error("missing required header key {key:?}")]
104 MissingHeaderKey { key: &'static str },
105
106 #[error("unknown format-version {found}: expected {expected}")]
107 UnknownFormatVersion { found: String, expected: u32 },
108
109 #[error("unexpected token at line {line}: {detail}")]
110 UnexpectedToken { line: usize, detail: String },
111
112 #[error("non-canonical input at line {line}: {detail}")]
113 NotCanonical { line: usize, detail: String },
114
115 #[error("invalid utf-8 at byte offset {offset}")]
116 InvalidUtf8 { offset: usize },
117
118 #[error("input exceeds size limit ({size} > {limit} bytes)")]
119 SizeLimit { size: usize, limit: usize },
120
121 #[error("name error: {0}")]
122 Name(#[from] NameError),
123
124 #[error("i/o error: {0}")]
125 Io(#[from] io::Error),
126}
127
128#[derive(Debug, Error)]
129pub enum StoreError {
130 #[error("snapshot store root does not exist: {0}")]
131 RootMissing(PathBuf),
132
133 #[error("snapshot not found: project {project}, tag {tag}")]
134 SnapshotNotFound { project: String, tag: String },
135
136 #[error("snapshot already exists: {0}")]
137 AlreadyExists(PathBuf),
138
139 #[error("path escape: {0:?}")]
140 PathEscape(PathBuf),
141
142 #[error("snapshot error: {0}")]
143 Snapshot(#[from] SnapshotError),
144
145 #[error("i/o error: {0}")]
146 Io(#[from] io::Error),
147}
148
149#[derive(Debug, Error)]
150pub enum ConfigError {
151 #[error("config file not found: {0}")]
152 NotFound(PathBuf),
153
154 #[error("malformed toml: {0}")]
155 Toml(#[from] toml::de::Error),
156
157 #[error("unknown config-version {0}")]
158 UnknownConfigVersion(u32),
159
160 #[error("project {0:?} referenced but not configured")]
161 UnknownProject(String),
162
163 #[error("series {0:?} referenced but not configured")]
164 UnknownSeries(String),
165
166 #[error("series {series:?} pins unknown project {project:?}")]
167 SeriesPinsUnknownProject { series: String, project: String },
168
169 #[error("pin pattern {pattern:?} for project {project:?} matched no snapshot tag")]
170 PinPatternNoMatch { project: String, pattern: String },
171
172 #[error("snapshot for pin {project} @ {tag} is not on disk at {expected_path}")]
173 PinSnapshotMissing {
174 project: String,
175 tag: String,
176 expected_path: String,
177 },
178
179 #[error(
180 "pin pattern {pattern:?} for project {project:?} could not be resolved: \
181 snapshot store error: {detail}"
182 )]
183 PinPatternStore {
184 project: String,
185 pattern: String,
186 detail: String,
187 },
188
189 #[error("pin `select` must be \"latest\" or \"oldest\", got {0:?}")]
190 PinUnknownSelect(String),
191
192 #[error("unknown project layout {0:?}: expected single_app, multi_app, or erlang_otp")]
193 UnknownProjectLayout(String),
194
195 #[error("unknown project kind {0:?}: expected external or self")]
196 UnknownProjectKind(String),
197
198 #[error(
199 "unknown project family {0:?}: expected generic, erlang_otp, ra, osiris, khepri, or rabbitmq"
200 )]
201 UnknownProjectFamily(String),
202
203 #[error("project {0:?} has kind=\"self\" but also specifies git_url; remove one")]
204 SelfProjectHasGitUrl(String),
205
206 #[error("project {0:?} requires git_url (external kind, the default)")]
207 ExternalProjectMissingGitUrl(String),
208
209 #[error("config defines more than one project with kind=\"self\": {projects:?}")]
210 MultipleSelfProjects { projects: Vec<String> },
211
212 #[error("self-project pin {project} @ {git_ref} requires `--repo-dir-path` for resolution")]
213 SelfPinNeedsRepoDirPath { project: String, git_ref: String },
214
215 #[error(
216 "series pins {project} as a self-pin (branch/sha form) but {project} is configured as kind=\"external\""
217 )]
218 SelfPinReferencesExternalProject { project: String },
219
220 #[error("self-project {0:?} has no git_url; operations needing one must use `--repo-dir-path`")]
221 SelfProjectHasNoGitUrl(String),
222
223 #[error("project {project:?} has layout {layout:?} but no `app_roots` defaulted or set")]
224 LayoutWithoutAppRoots { project: String, layout: String },
225
226 #[error("suite_rule[{rule_index}]: invalid regex: {detail}")]
227 SuiteRuleRegex { rule_index: usize, detail: String },
228
229 #[error("suite_rule[{rule_index}]: template references unknown capture group {placeholder:?}")]
230 SuiteRuleUnknownPlaceholder {
231 rule_index: usize,
232 placeholder: String,
233 },
234
235 #[error(
236 "suite_rule[{rule_index}]: include_suite_for_dep_modules requires a named capture `dep` from the path or line regex"
237 )]
238 SuiteRuleMissingDepCapture { rule_index: usize },
239
240 #[error("path_translation file not found: {0:?}")]
241 PathTranslationFileMissing(PathBuf),
242
243 #[error("path_translation: name must be non-empty")]
244 PathTranslationEmptyName,
245
246 #[error("path_translation[{name:?}]: {field} must be non-empty")]
247 PathTranslationEmptyPrefix { name: String, field: &'static str },
248
249 #[error("path_translation[{name:?}]: {field} {value:?} must end in '/'")]
250 PathTranslationPrefixNoTrailingSlash {
251 name: String,
252 field: &'static str,
253 value: String,
254 },
255
256 #[error("path_translation[{name:?}]: unknown direction {direction:?}")]
257 PathTranslationUnknownDirection { name: String, direction: String },
258
259 #[error("path_translation: duplicate name {0:?}")]
260 PathTranslationDuplicateName(String),
261
262 #[error("path_translation: duplicate source_prefix {0:?}")]
263 PathTranslationDuplicateSource(String),
264
265 #[error(
266 "path_translation: source_prefix overlap: {a:?} and {b:?} (one is a strict prefix of the other)"
267 )]
268 PathTranslationPrefixOverlap { a: String, b: String },
269
270 #[error("name error: {0}")]
271 Name(#[from] NameError),
272
273 #[error("i/o error: {0}")]
274 Io(#[from] io::Error),
275}
276
277#[derive(Debug, Error)]
278pub enum PatchError {
279 #[error("malformed unified diff at line {line}: {detail}")]
280 Malformed { line: usize, detail: String },
281
282 #[error("input exceeds size limit ({size} > {limit} bytes)")]
283 SizeLimit { size: usize, limit: usize },
284
285 #[error("patch is not valid UTF-8 (first invalid byte at offset {offset})")]
286 InvalidUtf8 { offset: usize },
287
288 #[error("unsupported file type: {0}")]
289 UnsupportedFileType(String),
290
291 #[error("binary patch is not analyzable")]
292 BinaryPatch,
293}