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}\n{stderr}")]
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        /// The stderr output from cargo publish
120        stderr: String,
121    },
122
123    /// Error while parsing a url for the release record.
124    #[error(transparent)]
125    UrlError(UrlError),
126
127    /// Error while attempting to write the release record as JSON.
128    #[error(transparent)]
129    WriteReleaseError(WriteReleaseError),
130
131    /// Error while attempting to update Cargo lockfile.
132    #[error("Unable to update Cargo lockfile")]
133    CargoLockfileUpdate {
134        /// The reason for the failed lockfile update.
135        reason: String,
136        /// The package name where lockfile updating failed.
137        package_name: String,
138    },
139}
140
141/// A specialized `Result` type for `semantic-release-cargo` operations.
142// #[cfg(feature = "napi-rs")]
143pub type Result<T> = std::result::Result<T, anyhow::Error>;
144// #[cfg(not(feature = "napi-rs"))]
145// pub type Result<T> = std::result::Result<T, Error>;
146
147/// The error details related to a problem parsing the workspace structure.
148#[derive(Debug, Error)]
149#[error("Unable to parse the workspace structure starting at {manifest_path}")]
150pub struct WorkspaceError {
151    #[source]
152    metadata_error: GuppyError,
153    manifest_path: PathBuf,
154}
155
156/// The error details related to a problem parsing a TOML file.
157#[derive(Debug, Error)]
158#[error("Unable to parse {} as a TOML file", path.display())]
159pub struct TomlError {
160    #[source]
161    inner: TomlEditError,
162    path: PathBuf,
163}
164
165/// The error details related the contents of a `Cargo.toml` file.
166#[derive(Debug, Error)]
167pub enum CargoTomlError {
168    /// Error related to a missing table in a `Cargo.toml` file.
169    #[error("Unable to locate expected table {table_name}")]
170    NoTable {
171        /// The name of the missing table.
172        table_name: String,
173    },
174
175    /// Error related to a missing value in a `Cargo.toml` file.
176    #[error("Unable to located expected value {value_name}")]
177    NoValue {
178        /// The name of the missing value.
179        value_name: String,
180    },
181
182    /// Error related to failed attempt to set the version for a package or a dependency.
183    #[error("Unable to set the version for {name} to {version}")]
184    SetVersion {
185        /// The name of the package or dependency.
186        name: String,
187
188        /// The version to which we attempted to set for the package or dependency.
189        version: String,
190    },
191}
192
193/// The error details related to an error parsing a url.
194#[derive(Debug, Error)]
195#[error("Unable to parse url for displaying release record.")]
196pub struct UrlError {
197    #[source]
198    inner: ParseError,
199}
200
201/// The error details related to writing a release record as JSON.
202#[derive(Debug, Error)]
203#[error("Unable to write the release record for {main_crate} as JSON.")]
204pub struct WriteReleaseError {
205    #[source]
206    inner: serde_json::Error,
207
208    main_crate: String,
209}
210
211impl Error {
212    pub(crate) fn workspace_error(metadata_error: GuppyError, manifest_path: PathBuf) -> Error {
213        Error::WorkspaceError(WorkspaceError {
214            metadata_error,
215            manifest_path,
216        })
217    }
218
219    pub(crate) fn verify_error(reason: impl Into<String>) -> Error {
220        Error::VerifyError {
221            reason: reason.into(),
222        }
223    }
224
225    pub(crate) fn bad_dependency(link: &PackageLink, typ: DependencyType) -> Error {
226        Error::BadDependency {
227            from: link.from().name().to_string(),
228            to: link.to().name().to_string(),
229            typ,
230        }
231    }
232
233    pub(crate) fn file_read_error(inner: io::Error, path: impl AsRef<Path>) -> Error {
234        Error::FileReadError {
235            inner,
236            path: path.as_ref().to_owned(),
237        }
238    }
239
240    pub(crate) fn file_write_error(inner: io::Error, path: impl AsRef<Path>) -> Error {
241        Error::FileWriteError {
242            inner,
243            path: path.as_ref().to_owned(),
244        }
245    }
246
247    pub(crate) fn toml_error(inner: TomlEditError, path: impl AsRef<Path>) -> Error {
248        Error::TomlError(TomlError {
249            inner,
250            path: path.as_ref().to_owned(),
251        })
252    }
253
254    pub(crate) fn cargo_publish(inner: io::Error, manifest_path: &Path) -> Error {
255        Error::CargoPublish {
256            inner,
257            manifest_path: manifest_path.to_owned(),
258        }
259    }
260
261    pub(crate) fn cargo_publish_status(
262        status: ExitStatus,
263        manifest_path: &Path,
264        stderr: &[u8],
265    ) -> Error {
266        Error::CargoPublishStatus {
267            status,
268            manifest_path: manifest_path.to_owned(),
269            stderr: String::from_utf8_lossy(stderr).into_owned(),
270        }
271    }
272
273    pub(crate) fn url_parse_error(inner: ParseError) -> Error {
274        Error::UrlError(UrlError { inner })
275    }
276
277    pub(crate) fn write_release_error(inner: serde_json::Error, main_crate: &str) -> Error {
278        Error::WriteReleaseError(WriteReleaseError {
279            inner,
280            main_crate: main_crate.to_owned(),
281        })
282    }
283}
284
285impl CargoTomlError {
286    pub(crate) fn no_table(table: &str) -> Self {
287        Self::NoTable {
288            table_name: table.to_owned(),
289        }
290    }
291
292    pub(crate) fn no_value(value: &str) -> Self {
293        Self::NoValue {
294            value_name: value.to_owned(),
295        }
296    }
297
298    pub(crate) fn set_version(name: &str, version: &str) -> Self {
299        Self::SetVersion {
300            name: name.to_owned(),
301            version: version.to_owned(),
302        }
303    }
304
305    pub(crate) fn into_error(self, path: impl AsRef<Path>) -> Error {
306        Error::CargoTomlError {
307            inner: self,
308            manifest_path: path.as_ref().to_owned(),
309        }
310    }
311}