semantic_release_rust/
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 `sementic-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 writing to the output.
41    #[error("Unable to write to the output")]
42    OutputError(#[source] io::Error),
43
44    /// Error while verifying the conditions for a release.
45    #[error("Conditions for a release are not satisfied: {reason}")]
46    VerifyError {
47        /// The reason the conditions are not satisfied.
48        reason: String,
49    },
50
51    /// Error while verifying that dependencies allow publication.
52    ///
53    /// This is a specific part of verifying the conditions for a release.
54    #[error("{typ} of {from} on {to} prevents publication of {from}")]
55    BadDependancy {
56        /// The name of the package whose dependency prevents publication.
57        from: String,
58
59        /// The depended on package that prevents publication.
60        to: String,
61
62        /// The type of dependency that prevents publication.
63        typ: DependencyType,
64    },
65
66    /// Error while reading a file.
67    #[error("Unable to read file {}", path.display())]
68    FileReadError {
69        /// The underlying error.
70        #[source]
71        inner: io::Error,
72
73        /// The path that could not be read.
74        path: PathBuf,
75    },
76
77    /// Error while writing a file.
78    #[error("Unable to write file {}", path.display())]
79    FileWriteError {
80        /// The underlying error.
81        #[source]
82        inner: io::Error,
83
84        /// The path the could not be written.
85        path: PathBuf,
86    },
87
88    /// Error while parsing a TOML document.
89    #[error(transparent)]
90    TomlError(TomlError),
91
92    /// Error while examining the contents of a `Cargo.toml` file.
93    #[error("Unexpected contents of {manifest_path}")]
94    CargoTomlError {
95        /// The error found in the `Cargo.toml` file.
96        #[source]
97        inner: CargoTomlError,
98
99        /// The `Cargo.toml` file in which the error occured.
100        manifest_path: PathBuf,
101    },
102
103    /// Error while attempting to run `cargo publish`
104    #[error("Unable to run \"cargo publish\" for {manifest_path}")]
105    CargoPublish {
106        /// The underlying error.
107        #[source]
108        inner: io::Error,
109
110        /// The manifest path for the crate on which the error occured.
111        manifest_path: PathBuf,
112    },
113
114    /// Error that records a non-sucess exit status from `cargo publish`.
115    #[error("\"cargo publish\" exited with a failure for {manifest_path}: {status}")]
116    CargoPublishStatus {
117        /// The exit status from `cargo publish`.
118        status: ExitStatus,
119
120        /// The manifest path for the crate on which the error occured.
121        manifest_path: PathBuf,
122    },
123
124    /// Error while parsing a url for the release record.
125    #[error(transparent)]
126    UrlError(UrlError),
127
128    /// Error while attempting to write the release record as JSON.
129    #[error(transparent)]
130    WriteReleaseError(WriteReleaseError),
131}
132
133/// A specialized `Result` type for `semantic-release-rust` operations.
134pub type Result<T> = std::result::Result<T, Error>;
135
136/// The error details related to a problem parsing the workspace structure.
137#[derive(Debug, Error)]
138#[error("Unable to parse the workspace structure starting at {manifest_path}")]
139pub struct WorkspaceError {
140    #[source]
141    metadata_error: GuppyError,
142    manifest_path: PathBuf,
143}
144
145/// The error details related to a problem parsing a TOML file.
146#[derive(Debug, Error)]
147#[error("Unable to parse {} as a TOML file", path.display())]
148pub struct TomlError {
149    #[source]
150    inner: TomlEditError,
151    path: PathBuf,
152}
153
154/// The error details related the contents of a `Cargo.toml` file.
155#[derive(Debug, Error)]
156pub enum CargoTomlError {
157    /// Error related to a missing table in a `Cargo.toml` file.
158    #[error("Unable to locate expected table {table_name}")]
159    NoTable {
160        /// The name of the missing table.
161        table_name: String,
162    },
163
164    /// Error related to a missing value in a `Cargo.toml` file.
165    #[error("Unable to located expected value {value_name}")]
166    NoValue {
167        /// The name of the missing value.
168        value_name: String,
169    },
170
171    /// Error related to failed attempt to set the version for a package or a dependency.
172    #[error("Unable to set the version for {name} to {version}")]
173    SetVersion {
174        /// The name of the package or dependency.
175        name: String,
176
177        /// The version to which we attempted to set for the package or dependency.
178        version: String,
179    },
180}
181
182/// The error details related to an error parsing a url.
183#[derive(Debug, Error)]
184#[error("Unable to parse url for displaying release record.")]
185pub struct UrlError {
186    #[source]
187    inner: ParseError,
188}
189
190/// The error details related to writing a release record as JSON.
191#[derive(Debug, Error)]
192#[error("Unable to write the release record for {main_crate} as JSON.")]
193pub struct WriteReleaseError {
194    #[source]
195    inner: serde_json::Error,
196
197    main_crate: String,
198}
199
200impl Error {
201    pub(crate) fn workspace_error(metadata_error: GuppyError, manifest_path: PathBuf) -> Error {
202        Error::WorkspaceError(WorkspaceError {
203            metadata_error,
204            manifest_path,
205        })
206    }
207
208    pub(crate) fn output_error(inner: io::Error) -> Error {
209        Error::OutputError(inner)
210    }
211
212    pub(crate) fn verify_error(reason: impl Into<String>) -> Error {
213        Error::VerifyError {
214            reason: reason.into(),
215        }
216    }
217
218    pub(crate) fn bad_dependency(link: &PackageLink, typ: DependencyType) -> Error {
219        Error::BadDependancy {
220            from: link.from().name().to_string(),
221            to: link.to().name().to_string(),
222            typ,
223        }
224    }
225
226    pub(crate) fn file_read_error(inner: io::Error, path: impl AsRef<Path>) -> Error {
227        Error::FileReadError {
228            inner,
229            path: path.as_ref().to_owned(),
230        }
231    }
232
233    pub(crate) fn file_write_error(inner: io::Error, path: impl AsRef<Path>) -> Error {
234        Error::FileWriteError {
235            inner,
236            path: path.as_ref().to_owned(),
237        }
238    }
239
240    pub(crate) fn toml_error(inner: TomlEditError, path: impl AsRef<Path>) -> Error {
241        Error::TomlError(TomlError {
242            inner,
243            path: path.as_ref().to_owned(),
244        })
245    }
246
247    pub(crate) fn cargo_publish(inner: io::Error, manifest_path: &Path) -> Error {
248        Error::CargoPublish {
249            inner,
250            manifest_path: manifest_path.to_owned(),
251        }
252    }
253
254    pub(crate) fn cargo_publish_status(status: ExitStatus, manifest_path: &Path) -> Error {
255        Error::CargoPublishStatus {
256            status,
257            manifest_path: manifest_path.to_owned(),
258        }
259    }
260
261    pub(crate) fn url_parse_error(inner: ParseError) -> Error {
262        Error::UrlError(UrlError { inner })
263    }
264
265    pub(crate) fn write_release_error(inner: serde_json::Error, main_crate: &str) -> Error {
266        Error::WriteReleaseError(WriteReleaseError {
267            inner,
268            main_crate: main_crate.to_owned(),
269        })
270    }
271
272    pub(crate) fn cycle_error(crate1: &str, crate2: &str) -> Error {
273        Error::WorkspaceCycles {
274            crate1: crate1.to_owned(),
275            crate2: crate2.to_owned(),
276        }
277    }
278}
279
280impl CargoTomlError {
281    pub(crate) fn no_table(table: &str) -> Self {
282        Self::NoTable {
283            table_name: table.to_owned(),
284        }
285    }
286
287    pub(crate) fn no_value(value: &str) -> Self {
288        Self::NoValue {
289            value_name: value.to_owned(),
290        }
291    }
292
293    pub(crate) fn set_version(name: &str, version: &str) -> Self {
294        Self::SetVersion {
295            name: name.to_owned(),
296            version: version.to_owned(),
297        }
298    }
299
300    pub(crate) fn into_error(self, path: impl AsRef<Path>) -> Error {
301        Error::CargoTomlError {
302            inner: self,
303            manifest_path: path.as_ref().to_owned(),
304        }
305    }
306}