cuenv_release/
error.rs

1//! Error types for release management operations.
2
3use miette::Diagnostic;
4use std::path::PathBuf;
5use thiserror::Error;
6
7/// Result type alias for release operations.
8pub type Result<T> = std::result::Result<T, Error>;
9
10/// Errors that can occur during release management operations.
11#[derive(Error, Debug, Diagnostic)]
12pub enum Error {
13    /// Failed to read or write a changeset file.
14    #[error("Changeset I/O error: {message}")]
15    #[diagnostic(
16        code(cuenv::release::changeset_io),
17        help("Check that the .cuenv/changesets directory exists and is writable")
18    )]
19    ChangesetIo {
20        /// The error message
21        message: String,
22        /// The path that caused the error
23        path: Option<PathBuf>,
24        /// The underlying source error
25        #[source]
26        source: Option<std::io::Error>,
27    },
28
29    /// Failed to parse a changeset file.
30    #[error("Invalid changeset format: {message}")]
31    #[diagnostic(
32        code(cuenv::release::changeset_parse),
33        help("Ensure the changeset file is valid Markdown with proper frontmatter")
34    )]
35    ChangesetParse {
36        /// The error message
37        message: String,
38        /// The path to the invalid file
39        path: Option<PathBuf>,
40    },
41
42    /// Failed to parse or validate a version string.
43    #[error("Invalid version: {version}")]
44    #[diagnostic(
45        code(cuenv::release::invalid_version),
46        help("Version must follow semantic versioning (e.g., 1.0.0, 2.1.0-beta.1)")
47    )]
48    InvalidVersion {
49        /// The invalid version string
50        version: String,
51    },
52
53    /// Package not found in the workspace.
54    #[error("Package not found: {name}")]
55    #[diagnostic(
56        code(cuenv::release::package_not_found),
57        help("Ensure the package exists in the workspace and is properly configured")
58    )]
59    PackageNotFound {
60        /// The package name that wasn't found
61        name: String,
62    },
63
64    /// No changesets found for release.
65    #[error("No changesets found")]
66    #[diagnostic(
67        code(cuenv::release::no_changesets),
68        help("Create changesets with 'cuenv changeset add' before running release version")
69    )]
70    NoChangesets,
71
72    /// Configuration error.
73    #[error("Release configuration error: {message}")]
74    #[diagnostic(code(cuenv::release::config), help("{help}"))]
75    Config {
76        /// The error message
77        message: String,
78        /// Help text for the user
79        help: String,
80    },
81
82    /// Manifest file error (Cargo.toml, package.json, etc.).
83    #[error("Manifest error: {message}")]
84    #[diagnostic(
85        code(cuenv::release::manifest),
86        help("Check that the manifest file exists and is properly formatted")
87    )]
88    Manifest {
89        /// The error message
90        message: String,
91        /// The manifest file path
92        path: Option<PathBuf>,
93    },
94
95    /// Git operation error.
96    #[error("Git error: {message}")]
97    #[diagnostic(
98        code(cuenv::release::git),
99        help("Ensure you are in a git repository and have the necessary permissions")
100    )]
101    Git {
102        /// The error message
103        message: String,
104    },
105
106    /// Publish error.
107    #[error("Publish failed: {message}")]
108    #[diagnostic(code(cuenv::release::publish))]
109    Publish {
110        /// The error message
111        message: String,
112        /// The package that failed to publish
113        package: Option<String>,
114    },
115
116    /// Wrapped I/O error.
117    #[error("I/O error: {0}")]
118    #[diagnostic(code(cuenv::release::io))]
119    Io(#[from] std::io::Error),
120
121    /// Wrapped JSON error.
122    #[error("JSON error: {0}")]
123    #[diagnostic(code(cuenv::release::json))]
124    Json(#[from] serde_json::Error),
125
126    /// Wrapped TOML parsing error.
127    #[error("TOML parse error: {0}")]
128    #[diagnostic(code(cuenv::release::toml_parse))]
129    TomlParse(#[from] toml::de::Error),
130
131    /// Wrapped TOML serialization error.
132    #[error("TOML serialization error: {0}")]
133    #[diagnostic(code(cuenv::release::toml_ser))]
134    TomlSer(#[from] toml::ser::Error),
135}
136
137impl Error {
138    /// Create a new changeset I/O error.
139    #[must_use]
140    pub fn changeset_io(message: impl Into<String>, path: Option<PathBuf>) -> Self {
141        Self::ChangesetIo {
142            message: message.into(),
143            path,
144            source: None,
145        }
146    }
147
148    /// Create a new changeset I/O error with source.
149    #[must_use]
150    pub fn changeset_io_with_source(
151        message: impl Into<String>,
152        path: Option<PathBuf>,
153        source: std::io::Error,
154    ) -> Self {
155        Self::ChangesetIo {
156            message: message.into(),
157            path,
158            source: Some(source),
159        }
160    }
161
162    /// Create a new changeset parse error.
163    #[must_use]
164    pub fn changeset_parse(message: impl Into<String>, path: Option<PathBuf>) -> Self {
165        Self::ChangesetParse {
166            message: message.into(),
167            path,
168        }
169    }
170
171    /// Create a new invalid version error.
172    #[must_use]
173    pub fn invalid_version(version: impl Into<String>) -> Self {
174        Self::InvalidVersion {
175            version: version.into(),
176        }
177    }
178
179    /// Create a new package not found error.
180    #[must_use]
181    pub fn package_not_found(name: impl Into<String>) -> Self {
182        Self::PackageNotFound { name: name.into() }
183    }
184
185    /// Create a new configuration error.
186    #[must_use]
187    pub fn config(message: impl Into<String>, help: impl Into<String>) -> Self {
188        Self::Config {
189            message: message.into(),
190            help: help.into(),
191        }
192    }
193
194    /// Create a new manifest error.
195    #[must_use]
196    pub fn manifest(message: impl Into<String>, path: Option<PathBuf>) -> Self {
197        Self::Manifest {
198            message: message.into(),
199            path,
200        }
201    }
202
203    /// Create a new git error.
204    #[must_use]
205    pub fn git(message: impl Into<String>) -> Self {
206        Self::Git {
207            message: message.into(),
208        }
209    }
210
211    /// Create a new publish error.
212    #[must_use]
213    pub fn publish(message: impl Into<String>, package: Option<String>) -> Self {
214        Self::Publish {
215            message: message.into(),
216            package,
217        }
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_changeset_io_error() {
227        let err = Error::changeset_io("failed to write", Some(PathBuf::from(".cuenv/test.md")));
228        assert!(err.to_string().contains("Changeset I/O error"));
229    }
230
231    #[test]
232    fn test_changeset_parse_error() {
233        let err = Error::changeset_parse("invalid frontmatter", None);
234        assert!(err.to_string().contains("Invalid changeset format"));
235    }
236
237    #[test]
238    fn test_invalid_version_error() {
239        let err = Error::invalid_version("not-a-version");
240        assert!(err.to_string().contains("not-a-version"));
241    }
242
243    #[test]
244    fn test_package_not_found_error() {
245        let err = Error::package_not_found("missing-pkg");
246        assert!(err.to_string().contains("missing-pkg"));
247    }
248
249    #[test]
250    fn test_config_error() {
251        let err = Error::config("bad config", "check your settings");
252        assert!(err.to_string().contains("bad config"));
253    }
254
255    #[test]
256    fn test_manifest_error() {
257        let err = Error::manifest("invalid toml", Some(PathBuf::from("Cargo.toml")));
258        assert!(err.to_string().contains("Manifest error"));
259    }
260
261    #[test]
262    fn test_git_error() {
263        let err = Error::git("not a repository");
264        assert!(err.to_string().contains("Git error"));
265    }
266
267    #[test]
268    fn test_publish_error() {
269        let err = Error::publish("auth failed", Some("my-pkg".to_string()));
270        assert!(err.to_string().contains("Publish failed"));
271    }
272
273    #[test]
274    fn test_no_changesets_error() {
275        let err = Error::NoChangesets;
276        assert!(err.to_string().contains("No changesets found"));
277    }
278}