cargo_hold/error.rs
1//! Error types for cargo-hold.
2//!
3//! This module defines all error types used throughout cargo-hold, using
4//! a combination of `thiserror` for ergonomic error definitions and `miette`
5//! for rich diagnostic output.
6//!
7//! # Error Handling Strategy
8//!
9//! - All errors derive from [`HoldError`]
10//! - Each variant includes helpful error messages and diagnostic codes
11//! - Context is preserved through the error chain
12//! - Errors are automatically converted to `miette::Result` for CLI output
13//!
14//! # Example
15//!
16//! ```no_run
17//! use std::path::Path;
18//!
19//! use cargo_hold::error::{HoldError, Result};
20//!
21//! fn check_repo(path: &Path) -> Result<()> {
22//! // Example of returning a specific error
23//! if !path.join(".git").exists() {
24//! return Err(HoldError::RepoNotFound(path.to_path_buf()));
25//! }
26//! Ok(())
27//! }
28//! ```
29
30use std::path::PathBuf;
31
32use miette::Diagnostic;
33use thiserror::Error;
34
35/// Error types that can occur in cargo-hold operations
36#[derive(Error, Debug, Diagnostic)]
37pub enum HoldError {
38 /// Git repository not found in the current directory or any parent.
39 ///
40 /// Raised when `git2::Repository::discover()` fails or when the repository
41 /// is bare (no working directory). cargo-hold requires a Git repository to
42 /// determine which files to track for timestamp management.
43 #[error("Git repository not found in '{0}' or any parent directories")]
44 #[diagnostic(
45 code(cargo_hold::git::repo_not_found),
46 help("Ensure 'cargo hold' is run from within a Git repository.")
47 )]
48 RepoNotFound(
49 /// The path where the Git repository was searched for
50 PathBuf,
51 ),
52
53 /// Failed to read the Git index to enumerate tracked files.
54 ///
55 /// Wraps errors from `repo.index()` when cargo-hold tries to read
56 /// the list of files tracked by Git. The Git index contains the staged
57 /// and tracked files that cargo-hold needs to manage.
58 #[error("Failed to access Git index")]
59 #[diagnostic(code(cargo_hold::git::index_error))]
60 IndexError(#[from] git2::Error),
61
62 /// File system I/O error during cargo-hold operations.
63 ///
64 /// Common causes: permission denied, file not found, disk full,
65 /// or memory mapping failures. Used throughout for file operations,
66 /// directory creation/removal, and metadata access.
67 #[error("I/O error accessing '{path}'")]
68 #[diagnostic(code(cargo_hold::io_error))]
69 IoError {
70 /// The path that caused the I/O error
71 path: PathBuf,
72 /// The underlying I/O error
73 #[source]
74 source: std::io::Error,
75 },
76
77 /// Failed to serialize StateMetadata to rkyv format.
78 ///
79 /// Occurs in `save_metadata()` when rkyv serialization fails.
80 /// This is typically an internal error. The metadata file can be
81 /// reset using `cargo hold bilge`.
82 #[error("Failed to serialize metadata")]
83 #[diagnostic(
84 code(cargo_hold::metadata::serialization_error),
85 help(
86 "An internal error occurred while trying to save the metadata. Try running 'cargo \
87 hold bilge' to reset."
88 )
89 )]
90 SerializationError(#[source] Box<dyn std::error::Error + Send + Sync>),
91
92 /// Failed to deserialize metadata from rkyv format.
93 ///
94 /// Occurs when loading metadata if the file is corrupted or from
95 /// an incompatible format. cargo-hold automatically attempts recovery
96 /// by resetting the metadata when this error is encountered.
97 #[error("Failed to deserialize metadata: {0}")]
98 #[diagnostic(
99 code(cargo_hold::metadata::deserialization_error),
100 help("The metadata file may be corrupted. Run 'cargo hold bilge' to reset it.")
101 )]
102 DeserializationError(
103 /// The underlying deserialization error
104 #[source]
105 rkyv::rancor::BoxedError,
106 ),
107
108 /// Git index path contains invalid UTF-8.
109 ///
110 /// Raised when converting Git index entry paths from bytes to UTF-8
111 /// strings fails. All paths tracked by Git must be valid UTF-8 for
112 /// cargo-hold to process them.
113 #[error("Invalid path: {message}")]
114 #[diagnostic(code(cargo_hold::path::invalid))]
115 InvalidPath {
116 /// Description of why the path is invalid
117 message: String,
118 },
119
120 /// Attempted to process a non-regular file (symlink or directory).
121 ///
122 /// cargo-hold only supports regular files. This error occurs when
123 /// trying to hash, get size of, or set timestamps on symlinks or
124 /// directories, which are explicitly unsupported.
125 #[error("Invalid file type for '{0}': {1}")]
126 #[diagnostic(
127 code(cargo_hold::file::invalid_type),
128 help("cargo-hold only processes regular files tracked by Git.")
129 )]
130 InvalidFileType(
131 /// The path of the invalid file
132 PathBuf,
133 /// Description of the file type issue
134 String,
135 ),
136
137 /// Failed to restore a file's modification time.
138 ///
139 /// Occurs during the salvage operation when cargo-hold cannot
140 /// open a file for writing or call `set_modified()`. Common causes
141 /// are insufficient permissions or file system restrictions.
142 #[error("Failed to set file modification time for '{0}'")]
143 #[diagnostic(
144 code(cargo_hold::timestamp::set_error),
145 help("Ensure you have write permissions for the file.")
146 )]
147 SetTimestampError(
148 /// The file whose timestamp couldn't be set
149 PathBuf,
150 /// The underlying I/O error
151 #[source]
152 std::io::Error,
153 ),
154
155 /// Failed to create parent directory for metadata file.
156 ///
157 /// Raised when `fs::create_dir_all()` fails while preparing to
158 /// save metadata. The metadata file is typically stored at
159 /// `target/cargo-hold.metadata`.
160 #[error("Failed to create metadata directory '{0}'")]
161 #[diagnostic(
162 code(cargo_hold::metadata::create_dir_error),
163 help("Ensure you have write permissions for the parent directory.")
164 )]
165 CreateMetadataDirError(
166 /// The directory path that couldn't be created
167 PathBuf,
168 /// The underlying I/O error
169 #[source]
170 std::io::Error,
171 ),
172
173 /// Invalid size specification for --max-target-size.
174 ///
175 /// Raised when parsing size strings like "5G" or "500M" fails.
176 /// Valid suffixes are B (bytes), K (kilobytes), M (megabytes),
177 /// G (gigabytes), or T (terabytes). Numbers without suffix are bytes.
178 #[error("Invalid metadata size: '{0}' - {1}")]
179 #[diagnostic(
180 code(cargo_hold::gc::invalid_metadata_size),
181 help(
182 "Specify metadata size as a number with optional suffix (e.g., '5G', '500M', '1024K', \
183 or raw bytes)"
184 )
185 )]
186 InvalidMetadataSize(
187 /// The invalid size value provided
188 String,
189 /// Description of the parsing error
190 String,
191 ),
192
193 /// Cannot determine home directory for cargo cache cleanup.
194 ///
195 /// Raised when `home::cargo_home()` returns None during garbage
196 /// collection of ~/.cargo/registry or ~/.cargo/bin. The home
197 /// directory is needed to locate cargo's cache directories.
198 #[error("Garbage collection error: {0}")]
199 #[diagnostic(
200 code(cargo_hold::gc::error),
201 help("Check permissions and disk space, then try again.")
202 )]
203 GcError(
204 /// Description of the garbage collection error
205 String,
206 ),
207
208 /// Metadata version is newer than supported or configuration invalid.
209 ///
210 /// Raised when: 1) loaded metadata has version > METADATA_VERSION,
211 /// indicating it was created by a newer cargo-hold version, or
212 /// 2) required parameters are missing for the voyage command.
213 #[error("Configuration error: {0}")]
214 #[diagnostic(
215 code(cargo_hold::config::error),
216 help("Check the required configuration parameters.")
217 )]
218 ConfigError(
219 /// Description of the configuration error
220 String,
221 ),
222
223 /// One or more tracked files could not be hashed or read during
224 /// stow/salvage.
225 ///
226 /// The command stops instead of writing partial metadata or restoring
227 /// timestamps for only a subset of files, which would make CI report
228 /// success while incremental compilation state is wrong.
229 #[error("failed to process {failed} of {total} tracked file(s); run with -v for details")]
230 #[diagnostic(
231 code(cargo_hold::files::partial_failure),
232 help("Fix file permissions or paths, then re-run the command.")
233 )]
234 PartialFileProcessing {
235 /// Number of files that failed
236 failed: usize,
237 /// Total tracked files attempted
238 total: usize,
239 },
240
241 /// PathBuf cannot be converted to UTF-8 string for storage.
242 ///
243 /// Raised in StateMetadata operations when a PathBuf contains
244 /// non-UTF-8 sequences. All paths must be valid UTF-8 for storage
245 /// in the metadata format and compatibility with Git.
246 #[error("Invalid UTF-8 in path: {0}")]
247 #[diagnostic(
248 code(cargo_hold::path::invalid_utf8),
249 help("File paths must be valid UTF-8. This is a requirement for Git-tracked files.")
250 )]
251 InvalidUtf8Path(
252 /// The path containing invalid UTF-8
253 PathBuf,
254 ),
255}
256
257/// Type alias for Results in this crate
258pub type Result<T> = std::result::Result<T, HoldError>;