1use std::path::PathBuf;
2
3pub type Result<T, E = Error> = std::result::Result<T, E>;
4
5#[derive(Debug, thiserror::Error, miette::Diagnostic)]
6pub enum Error {
7 #[error("Templating rendering error")]
10 #[diagnostic(code(template))]
11 TeraError(#[from] tera::Error),
12
13 #[error("Could not parse datetime")]
14 #[diagnostic(code(datetime_parsing))]
15 Chrono(#[from] chrono::ParseError),
16
17 #[error("Date or duration out of range")]
18 #[diagnostic(code(duration_out_of_range))]
19 OutOfRangeDate(#[from] chrono::OutOfRangeError),
20
21 #[error("{msg}")]
22 #[diagnostic(code(io))]
23 Io {
24 msg: String,
25 #[source]
26 err: std::io::Error,
27 },
28
29 #[error("Could not create temporary directory")]
30 #[diagnostic(code(tempdir))]
31 TempDirectory(#[source] std::io::Error),
32
33 #[error("{count} source(s) could not be checked")]
34 #[diagnostic(
35 code(check::sources_failed),
36 help("The errors are reported above. Fix them or drop the sources.")
37 )]
38 SourcesFailed { count: usize },
39
40 #[error("{msg}")]
41 #[diagnostic(code(git))]
42 Git {
43 msg: String,
44 #[source]
45 err: git2::Error,
46 },
47
48 #[error("{msg}")]
49 #[diagnostic(code(github))]
50 GitHub {
51 msg: String,
52 #[source]
53 err: octocrab::Error,
54 },
55
56 #[error("{msg}")]
57 #[diagnostic(code(gitlab))]
58 GitLab {
59 msg: String,
60 #[source]
61 err: gitlab::GitlabError,
62 },
63
64 #[error("{msg}")]
65 #[diagnostic(code(gitlab))]
66 GitLabBoxed {
67 msg: String,
68 #[source]
69 err: Box<dyn std::error::Error + Send + Sync + 'static>,
70 },
71
72 #[error("Valid env var required `{name}`: {msg}")]
73 #[diagnostic(code(env))]
74 Env {
75 name: String,
76 msg: String,
77 #[source]
78 err: std::env::VarError,
79 },
80
81 #[error(transparent)]
82 #[diagnostic(code(http))]
83 ReqwestError(#[from] reqwest::Error),
84
85 #[error(transparent)]
86 #[diagnostic(code(yaml))]
87 SerdeYamlError(#[from] serde_yaml_ng::Error),
88
89 #[error(transparent)]
90 #[diagnostic(code(json))]
91 SerdeJsonError(#[from] serde_json::Error),
92
93 #[error(transparent)]
94 Glob(#[from] wax::BuildError),
95
96 #[error(transparent)]
97 Walk(#[from] wax::walk::WalkError),
98
99 #[error(transparent)]
100 EnvExpand(#[from] shellexpand::LookupError<std::env::VarError>),
101
102 #[error("Could not decode config file at `{}`", path.display())]
105 DeserializeConfig {
106 path: PathBuf,
107 #[source]
108 err: serde_yaml_ng::Error,
109 },
110
111 #[error(
112 "Unsupported config version (required: {}, got: {})",
113 required,
114 current
115 )]
116 UnsupportedConfigVersion { required: String, current: String },
117
118 #[error("No input in pipe")]
121 #[diagnostic(code(adnv::no_input_in_pipe))]
122 NoInputInPipe,
123
124 #[error("Could not delete directory: {}", path.display())]
125 CouldNotDeleteDirectory {
126 path: PathBuf,
127 #[source]
128 err: std::io::Error,
129 },
130
131 #[error("Could not copy directory: {} -> {}", source.display(), destination.display())]
132 CouldNotCopyDirectory {
133 source: PathBuf,
134 destination: PathBuf,
135 #[source]
136 err: std::io::Error,
137 },
138
139 #[error("Could not copy file: {} -> {}", source.display(), destination.display())]
140 CouldNotCopyFile {
141 source: PathBuf,
142 destination: PathBuf,
143 #[source]
144 err: std::io::Error,
145 },
146
147 #[error("Could not open or create file: {}", path.display())]
148 CouldNotOpenFile {
149 path: PathBuf,
150 #[source]
151 err: std::io::Error,
152 },
153
154 #[error("Refusing to copy a symbolic link: {}", path.display())]
155 #[diagnostic(
156 code(fs::symlink_in_source),
157 help(
158 "A tree fetched from a source may not hold symbolic links: copying one verbatim would let a remote source reach outside the generated output."
159 )
160 )]
161 SymlinkInSource { path: PathBuf },
162
163 #[error("Could not update {}: the file changed since it was read", path.display())]
164 #[diagnostic(
165 code(edit::file_changed),
166 help("Run the command again so the versions are located in the file as it is now.")
167 )]
168 FileChangedSinceParsed {
169 path: PathBuf,
170 #[source]
171 err: ropey::Error,
172 },
173
174 #[error("Could not write content in file: {}", path.display())]
175 CouldNotWriteFile {
176 path: PathBuf,
177 #[source]
178 err: std::io::Error,
179 },
180
181 #[error("Could not strip prefix: {} from path: {}", path.display(), to_strip.display())]
182 CouldNotStripPrefix {
183 path: PathBuf,
184 to_strip: PathBuf,
185 #[source]
186 err: std::path::StripPrefixError,
187 },
188
189 #[error("Could not fetch URL `{url}`: {msg}")]
190 #[diagnostic(code(fetch_error))]
191 UrlFetch {
192 msg: String,
193 url: url::Url,
194 #[source]
195 err: reqwest::Error,
196 },
197
198 #[error("Invalid archive: {msg}")]
199 #[diagnostic(code(invalid_archive))]
200 InvalidArchive { msg: String, url: url::Url },
201
202 #[error("Could not create intermediate directory")]
203 IntermediateDirectory(#[source] std::io::Error),
204
205 #[error("Unknown source type: {0}")]
208 UnknownSourceType(url::Url),
209
210 #[error("The URL fragment is missing for source: {0}")]
211 URLFragmentMissing(url::Url),
212
213 #[error("{msg}")]
214 BadQueryString {
215 msg: String,
216 #[source]
217 err: serde_qs::Error,
218 },
219
220 #[error("Could not send post to Mattermost")]
221 MattermostPost(
222 #[from]
223 mattermost_client::openapi::apis::Error<
224 mattermost_client::openapi::apis::posts_api::CreatePostError,
225 >,
226 ),
227
228 #[error("There is no outputs in config file")]
229 NoOutputsConfigFile,
230
231 #[error("There is no matching output in config file for \"{0}\"")]
232 NoMatchingOutputConfigFile(String),
233
234 #[error(
235 "The URL '{0}' doesn't match URL structure for npm registry. Must be npm://<PACKAGE NAME> or npm+<REGISTRY URL>/<PACKAGE NAME>"
236 )]
237 NpmRegistryURL(String),
238
239 #[error("Invalid entry user_id in config file: {user_id}")]
242 InvalidUserId {
243 user_id: String,
244 #[source]
245 err: matrix_sdk::ruma::IdParseError,
246 },
247
248 #[error("Entry user_password in config file doesn't match user_id : {user_id} ")]
249 NonMatchingUserIdPassword {
250 user_id: String,
251 #[source]
252 err: matrix_sdk::Error,
253 },
254
255 #[error("Invalid entry room_id in config file: {room_id}")]
256 InvalidRoomId {
257 room_id: String,
258 #[source]
259 err: matrix_sdk::ruma::IdParseError,
260 },
261
262 #[error("Impossible to join room: {room_id}")]
263 ImpossibleToJoinRoom {
264 room_id: String,
265 #[source]
266 err: matrix_sdk::Error,
267 },
268
269 #[error("Impossible to get joined room {0} from client")]
270 ImpossibleToGetRoom(String),
271
272 #[error(transparent)]
273 BuildServer(#[from] matrix_sdk::ClientBuildError),
274
275 #[error(transparent)]
276 SyncServerState(#[from] matrix_sdk::Error),
277
278 #[error(transparent)]
279 ClientLogout(#[from] matrix_sdk::HttpError),
280
281 #[error("Invalid sender mail: \"{mail}\"")]
282 InvalidSenderMail {
283 mail: String,
284 #[source]
285 err: lettre::address::AddressError,
286 },
287 #[error("Invalid recipient mail: \"{mail}\"")]
288 InvalidRecipientMail {
289 mail: String,
290 #[source]
291 err: lettre::address::AddressError,
292 },
293
294 #[error(transparent)]
295 SMTP(#[from] lettre::transport::smtp::Error),
296
297 #[error(transparent)]
300 TerraformRegistry(#[from] terraform_registry::Error),
301
302 #[error("Version should be described following terraform version constraint syntax")]
303 TerraformVersion(#[from] terraform_version::Error),
304
305 #[error("Version requirements is not a valid SemVer version requirement")]
306 SemVerVersion(#[from] semver::Error),
307
308 #[error("There are no version available")]
309 NoVersionAvailable,
310
311 #[error("The URL fragment '{0}' doesn't match any entry from repository '{1}'")]
312 HelmEntryError(String, String),
313
314 #[error("Invalid source `{0}`: {1}")]
315 InvalidSource(url::Url, String),
316
317 #[error("Unsupported Docker Registry: {0}")]
318 UnsupportedDockerRegistry(url::Url),
319
320 #[error("No version described")]
321 NoVersionDescribed,
322
323 #[error("No comparator in the VersionRequirement")]
324 NoComparator,
325
326 #[error(transparent)]
329 HclParse(#[from] crate::parsers::hcl::Error),
330
331 #[error(transparent)]
332 YamlParse(#[from] marked_yaml::LoadError),
333
334 #[error(transparent)]
335 URLParse(#[from] url::ParseError),
336
337 #[error("Invalid URL: {}, parsing impossible", repo_url)]
338 InvalidUrl {
339 repo_url: String,
340 #[source]
341 err: url::ParseError,
342 },
343
344 #[error("Inline hint error on file {file}")]
345 HclParsing {
346 file: String,
347 #[source]
348 err: crate::parsers::hcl::ast::error::Error,
349 },
350
351 #[error(
352 "Invalid inline hint source: {}, see documentation for more information about inline hints format",
353 repo_url
354 )]
355 InvalidInlineHintSource {
356 repo_url: String,
357 #[source]
358 err: Box<Error>,
359 },
360}
361
362impl Error {
363 pub(crate) fn io(err: std::io::Error, msg: impl Into<String>) -> Error {
364 Error::Io {
365 msg: msg.into(),
366 err,
367 }
368 }
369 pub(crate) fn git(err: git2::Error, msg: impl Into<String>) -> Self {
370 Self::Git {
371 msg: msg.into(),
372 err,
373 }
374 }
375 pub(crate) fn github(err: octocrab::Error, msg: impl Into<String>) -> Error {
376 Error::GitHub {
377 msg: msg.into(),
378 err,
379 }
380 }
381
382 pub(crate) fn gitlab(err: impl Into<gitlab::GitlabError>, msg: impl Into<String>) -> Error {
383 Error::GitLab {
384 msg: msg.into(),
385 err: err.into(),
386 }
387 }
388
389 pub(crate) fn gitlab_boxed(
390 err: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
391 msg: impl Into<String>,
392 ) -> Error {
393 Error::GitLabBoxed {
394 msg: msg.into(),
395 err: err.into(),
396 }
397 }
398
399 pub(crate) fn env(
400 err: std::env::VarError,
401 name: impl Into<String>,
402 msg: impl Into<String>,
403 ) -> Error {
404 Error::Env {
405 name: name.into(),
406 msg: msg.into(),
407 err,
408 }
409 }
410
411 pub(crate) fn deserialize_config(err: serde_yaml_ng::Error, path: impl Into<PathBuf>) -> Error {
414 Error::DeserializeConfig {
415 path: path.into(),
416 err,
417 }
418 }
419
420 pub(crate) fn serde_qs(err: serde_qs::Error, msg: impl Into<String>) -> Self {
423 Self::BadQueryString {
424 msg: msg.into(),
425 err,
426 }
427 }
428
429 #[inline]
432 pub(crate) fn copy_directory(
433 err: std::io::Error,
434 source: impl Into<PathBuf>,
435 dest: impl Into<PathBuf>,
436 ) -> Error {
437 Error::CouldNotCopyDirectory {
438 source: source.into(),
439 destination: dest.into(),
440 err,
441 }
442 }
443
444 #[inline]
445 pub(crate) fn invalid_archive(url: url::Url, msg: impl Into<String>) -> Error {
446 Error::InvalidArchive {
447 msg: msg.into(),
448 url,
449 }
450 }
451
452 #[inline]
453 pub(crate) fn url_fetch(err: reqwest::Error, url: url::Url, msg: impl Into<String>) -> Error {
454 Error::UrlFetch {
455 msg: msg.into(),
456 url,
457 err,
458 }
459 }
460
461 pub(crate) fn invalid_source(url: url::Url, msg: impl Into<String>) -> Error {
464 Error::InvalidSource(url, msg.into())
465 }
466
467 pub(crate) fn invalid_inline_hint(err: Error, repo_url: impl Into<String>) -> Error {
470 Error::InvalidInlineHintSource {
471 repo_url: repo_url.into(),
472 err: Box::new(err),
473 }
474 }
475}
476
477#[derive(Debug)]
480pub struct ErrorWithData<T>(Box<(Error, T)>);
481
482impl<T> ErrorWithData<T> {
483 pub fn error(&self) -> &Error {
484 &self.0.0
485 }
486
487 pub fn data(&self) -> &T {
488 &self.0.1
489 }
490
491 pub fn split(self) -> (Error, T) {
492 *self.0
493 }
494}
495
496impl Error {
497 pub fn with_data<T>(self, data: T) -> ErrorWithData<T> {
498 ErrorWithData(Box::new((self, data)))
499 }
500}
501
502#[derive(Debug)]
503pub struct Results<T, E = Error>(Vec<T>, Vec<E>);
504
505impl<T, E> Results<T, E> {
506 pub fn values(&self) -> &[T] {
507 &self.0
508 }
509
510 pub fn errors(&self) -> &[E] {
511 &self.1
512 }
513
514 pub fn split(self) -> (Vec<T>, Vec<E>) {
515 (self.0, self.1)
516 }
517}
518
519impl<T, E> From<(Vec<T>, Vec<E>)> for Results<T, E> {
524 fn from((values, errors): (Vec<T>, Vec<E>)) -> Self {
525 Self(values, errors)
526 }
527}
528
529impl<T, E> From<Vec<Result<T, E>>> for Results<T, E> {
530 fn from(results: Vec<Result<T, E>>) -> Self {
531 use itertools::Itertools;
532
533 let (values, errors) = results.into_iter().partition_result();
534 Self(values, errors)
535 }
536}