semantic_release_cargo/
error.rs

1// Copyright 2020 Steven Bosnick
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE-2.0 or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::{
10    io,
11    path::{Path, PathBuf},
12    process::ExitStatus,
13};
14
15use guppy::errors::Error as GuppyError;
16use guppy::graph::PackageLink;
17use thiserror::Error;
18use toml_edit::TomlError as TomlEditError;
19use url::ParseError;
20
21use super::DependencyType;
22
23/// The error type for operations `semantic-release-rust` operations.
24#[derive(Debug, Error)]
25pub enum Error {
26    /// Error while parsing the structure of a workspace.
27    #[error(transparent)]
28    WorkspaceError(WorkspaceError),
29
30    /// Error when verifying that a workspace does not include cycles.
31    #[error("Workspace has at least one cycle that includes as least {crate1} and {crate2}")]
32    WorkspaceCycles {
33        /// The first crate in the cycle.
34        crate1: String,
35
36        /// The second crate in the cycle.
37        crate2: String,
38    },
39
40    /// Error while verifying the conditions for a release.
41    #[error("Conditions for a release are not satisfied: {reason}")]
42    VerifyError {
43        /// The reason the conditions are not satisfied.
44        reason: String,
45    },
46
47    /// Error while verifying that dependencies allow publication.
48    ///
49    /// This is a specific part of verifying the conditions for a release.
50    #[error("{typ} of {from} on {to} prevents publication of {from}")]
51    BadDependency {
52        /// The name of the package whose dependency prevents publication.
53        from: String,
54
55        /// The depended on package that prevents publication.
56        to: String,
57
58        /// The type of dependency that prevents publication.
59        typ: DependencyType,
60    },
61
62    /// Error while reading a file.
63    #[error("Unable to read file {}", path.display())]
64    FileReadError {
65        /// The underlying error.
66        #[source]
67        inner: io::Error,
68
69        /// The path that could not be read.
70        path: PathBuf,
71    },
72
73    /// Error while writing a file.
74    #[error("Unable to write file {}", path.display())]
75    FileWriteError {
76        /// The underlying error.
77        #[source]
78        inner: io::Error,
79
80        /// The path the could not be written.
81        path: PathBuf,
82    },
83
84    /// Error while parsing a TOML document.
85    #[error(transparent)]
86    TomlError(TomlError),
87
88    /// Error while examining the contents of a `Cargo.toml` file.
89    #[error("Unexpected contents of {manifest_path}")]
90    CargoTomlError {
91        /// The error found in the `Cargo.toml` file.
92        #[source]
93        inner: CargoTomlError,
94
95        /// The `Cargo.toml` file in which the error occurred.
96        manifest_path: PathBuf,
97    },
98
99    /// Error while attempting to run `cargo publish`
100    #[error("Unable to run \"cargo publish\" for {manifest_path}")]
101    CargoPublish {
102        /// The underlying error.
103        #[source]
104        inner: io::Error,
105
106        /// The manifest path for the crate on which the error occurred.
107        manifest_path: PathBuf,
108    },
109
110    /// Error that records a non-sucess exit status from `cargo publish`.
111    #[error("\"cargo publish\" exited with a failure for {manifest_path}: {status}")]
112    CargoPublishStatus {
113        /// The exit status from `cargo publish`.
114        status: ExitStatus,
115
116        /// The manifest path for the crate on which the error occurred.
117        manifest_path: PathBuf,
118    },
119
120    /// Error while parsing a url for the release record.
121    #[error(transparent)]
122    UrlError(UrlError),
123
124    /// Error while attempting to write the release record as JSON.
125    #[error(transparent)]
126    WriteReleaseError(WriteReleaseError),
127
128    /// Error while attempting to update Cargo lockfile.
129    #[error("Unable to update Cargo lockfile")]
130    CargoLockfileUpdate {
131        /// The reason for the failed lockfile update.
132        reason: String,
133        /// The package name where lockfile updating failed.
134        package_name: String,
135    },
136}
137
138/// A specialized `Result` type for `semantic-release-cargo` operations.
139// #[cfg(feature = "napi-rs")]
140pub type Result<T> = std::result::Result<T, anyhow::Error>;
141// #[cfg(not(feature = "napi-rs"))]
142// pub type Result<T> = std::result::Result<T, Error>;
143
144/// The error details related to a problem parsing the workspace structure.
145#[derive(Debug, Error)]
146#[error("Unable to parse the workspace structure starting at {manifest_path}")]
147pub struct WorkspaceError {
148    #[source]
149    metadata_error: GuppyError,
150    manifest_path: PathBuf,
151}
152
153/// The error details related to a problem parsing a TOML file.
154#[derive(Debug, Error)]
155#[error("Unable to parse {} as a TOML file", path.display())]
156pub struct TomlError {
157    #[source]
158    inner: TomlEditError,
159    path: PathBuf,
160}
161
162/// The error details related the contents of a `Cargo.toml` file.
163#[derive(Debug, Error)]
164pub enum CargoTomlError {
165    /// Error related to a missing table in a `Cargo.toml` file.
166    #[error("Unable to locate expected table {table_name}")]
167    NoTable {
168        /// The name of the missing table.
169        table_name: String,
170    },
171
172    /// Error related to a missing value in a `Cargo.toml` file.
173    #[error("Unable to located expected value {value_name}")]
174    NoValue {
175        /// The name of the missing value.
176        value_name: String,
177    },
178
179    /// Error related to failed attempt to set the version for a package or a dependency.
180    #[error("Unable to set the version for {name} to {version}")]
181    SetVersion {
182        /// The name of the package or dependency.
183        name: String,
184
185        /// The version to which we attempted to set for the package or dependency.
186        version: String,
187    },
188}
189
190/// The error details related to an error parsing a url.
191#[derive(Debug, Error)]
192#[error("Unable to parse url for displaying release record.")]
193pub struct UrlError {
194    #[source]
195    inner: ParseError,
196}
197
198/// The error details related to writing a release record as JSON.
199#[derive(Debug, Error)]
200#[error("Unable to write the release record for {main_crate} as JSON.")]
201pub struct WriteReleaseError {
202    #[source]
203    inner: serde_json::Error,
204
205    main_crate: String,
206}
207
208impl Error {
209    pub(crate) fn workspace_error(metadata_error: GuppyError, manifest_path: PathBuf) -> Error {
210        Error::WorkspaceError(WorkspaceError {
211            metadata_error,
212            manifest_path,
213        })
214    }
215
216    pub(crate) fn verify_error(reason: impl Into<String>) -> Error {
217        Error::VerifyError {
218            reason: reason.into(),
219        }
220    }
221
222    pub(crate) fn bad_dependency(link: &PackageLink, typ: DependencyType) -> Error {
223        Error::BadDependency {
224            from: link.from().name().to_string(),
225            to: link.to().name().to_string(),
226            typ,
227        }
228    }
229
230    pub(crate) fn file_read_error(inner: io::Error, path: impl AsRef<Path>) -> Error {
231        Error::FileReadError {
232            inner,
233            path: path.as_ref().to_owned(),
234        }
235    }
236
237    pub(crate) fn file_write_error(inner: io::Error, path: impl AsRef<Path>) -> Error {
238        Error::FileWriteError {
239            inner,
240            path: path.as_ref().to_owned(),
241        }
242    }
243
244    pub(crate) fn toml_error(inner: TomlEditError, path: impl AsRef<Path>) -> Error {
245        Error::TomlError(TomlError {
246            inner,
247            path: path.as_ref().to_owned(),
248        })
249    }
250
251    pub(crate) fn cargo_publish(inner: io::Error, manifest_path: &Path) -> Error {
252        Error::CargoPublish {
253            inner,
254            manifest_path: manifest_path.to_owned(),
255        }
256    }
257
258    pub(crate) fn cargo_publish_status(status: ExitStatus, manifest_path: &Path) -> Error {
259        Error::CargoPublishStatus {
260            status,
261            manifest_path: manifest_path.to_owned(),
262        }
263    }
264
265    pub(crate) fn url_parse_error(inner: ParseError) -> Error {
266        Error::UrlError(UrlError { inner })
267    }
268
269    pub(crate) fn write_release_error(inner: serde_json::Error, main_crate: &str) -> Error {
270        Error::WriteReleaseError(WriteReleaseError {
271            inner,
272            main_crate: main_crate.to_owned(),
273        })
274    }
275}
276
277impl CargoTomlError {
278    pub(crate) fn no_table(table: &str) -> Self {
279        Self::NoTable {
280            table_name: table.to_owned(),
281        }
282    }
283
284    pub(crate) fn no_value(value: &str) -> Self {
285        Self::NoValue {
286            value_name: value.to_owned(),
287        }
288    }
289
290    pub(crate) fn set_version(name: &str, version: &str) -> Self {
291        Self::SetVersion {
292            name: name.to_owned(),
293            version: version.to_owned(),
294        }
295    }
296
297    pub(crate) fn into_error(self, path: impl AsRef<Path>) -> Error {
298        Error::CargoTomlError {
299            inner: self,
300            manifest_path: path.as_ref().to_owned(),
301        }
302    }
303}