1use miette::Diagnostic;
4use std::path::PathBuf;
5use thiserror::Error;
6
7pub type Result<T> = std::result::Result<T, Error>;
9
10#[derive(Error, Debug, Diagnostic)]
12pub enum Error {
13 #[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 message: String,
22 path: Option<PathBuf>,
24 #[source]
26 source: Option<std::io::Error>,
27 },
28
29 #[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 message: String,
38 path: Option<PathBuf>,
40 },
41
42 #[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 version: String,
51 },
52
53 #[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 name: String,
62 },
63
64 #[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 #[error("Release configuration error: {message}")]
74 #[diagnostic(code(cuenv::release::config), help("{help}"))]
75 Config {
76 message: String,
78 help: String,
80 },
81
82 #[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 message: String,
91 path: Option<PathBuf>,
93 },
94
95 #[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 message: String,
104 },
105
106 #[error("Publish failed: {message}")]
108 #[diagnostic(code(cuenv::release::publish))]
109 Publish {
110 message: String,
112 package: Option<String>,
114 },
115
116 #[error("I/O error: {0}")]
118 #[diagnostic(code(cuenv::release::io))]
119 Io(#[from] std::io::Error),
120
121 #[error("JSON error: {0}")]
123 #[diagnostic(code(cuenv::release::json))]
124 Json(#[from] serde_json::Error),
125
126 #[error("TOML parse error: {0}")]
128 #[diagnostic(code(cuenv::release::toml_parse))]
129 TomlParse(#[from] toml::de::Error),
130
131 #[error("TOML serialization error: {0}")]
133 #[diagnostic(code(cuenv::release::toml_ser))]
134 TomlSer(#[from] toml::ser::Error),
135}
136
137impl Error {
138 #[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 #[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 #[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 #[must_use]
173 pub fn invalid_version(version: impl Into<String>) -> Self {
174 Self::InvalidVersion {
175 version: version.into(),
176 }
177 }
178
179 #[must_use]
181 pub fn package_not_found(name: impl Into<String>) -> Self {
182 Self::PackageNotFound { name: name.into() }
183 }
184
185 #[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 #[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 #[must_use]
205 pub fn git(message: impl Into<String>) -> Self {
206 Self::Git {
207 message: message.into(),
208 }
209 }
210
211 #[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}