arch_toolkit/error.rs
1//! Unified error type for arch-toolkit.
2
3use thiserror::Error;
4
5/// Unified error type for all arch-toolkit operations.
6///
7/// This error type covers all possible failure modes across different modules,
8/// providing clear, actionable error messages.
9#[derive(Error, Debug)]
10pub enum ArchToolkitError {
11 /// Network or HTTP request error.
12 ///
13 /// Note: For AUR operations, prefer using operation-specific error variants
14 /// (`SearchFailed`, `InfoFailed`, `CommentsFailed`, `PkgbuildFailed`) to preserve context.
15 /// This variant is retained for client initialization and non-AUR operations.
16 #[cfg(feature = "aur")]
17 #[error("Network error: {0}")]
18 Network(reqwest::Error),
19
20 /// AUR search operation failed.
21 #[cfg(feature = "aur")]
22 #[error("AUR search failed for query '{query}': {source}")]
23 SearchFailed {
24 /// The search query that failed.
25 query: String,
26 /// The underlying network error.
27 #[source]
28 source: reqwest::Error,
29 },
30
31 /// AUR info fetch operation failed.
32 #[cfg(feature = "aur")]
33 #[error("AUR info fetch failed for packages [{packages}]: {source}")]
34 InfoFailed {
35 /// Comma-separated list of package names that failed.
36 packages: String,
37 /// The underlying network error.
38 #[source]
39 source: reqwest::Error,
40 },
41
42 /// AUR comments fetch operation failed.
43 #[cfg(feature = "aur")]
44 #[error("AUR comments fetch failed for package '{package}': {source}")]
45 CommentsFailed {
46 /// The package name that failed.
47 package: String,
48 /// The underlying network error.
49 #[source]
50 source: reqwest::Error,
51 },
52
53 /// PKGBUILD fetch operation failed.
54 #[cfg(feature = "aur")]
55 #[error("PKGBUILD fetch failed for package '{package}': {source}")]
56 PkgbuildFailed {
57 /// The package name that failed.
58 package: String,
59 /// The underlying network error.
60 #[source]
61 source: reqwest::Error,
62 },
63
64 /// JSON parsing error.
65 #[error("JSON parsing error: {0}")]
66 Json(#[from] serde_json::Error),
67
68 /// File I/O error with path context.
69 #[error("I/O error at '{path}': {source}")]
70 Io {
71 /// The file path where the I/O operation failed.
72 path: String,
73 /// The underlying I/O error.
74 #[source]
75 source: std::io::Error,
76 },
77
78 /// Custom parsing error with message.
79 #[error("Parse error: {0}")]
80 Parse(String),
81
82 /// Rate limiting error with optional retry-after information.
83 #[error("Rate limited by server{0}", .retry_after.map(|s| format!(" (retry after {s}s)")).unwrap_or_default())]
84 RateLimited {
85 /// Optional retry-after value in seconds from server.
86 retry_after: Option<u64>,
87 },
88
89 /// Package not found (enhanced with package name).
90 #[error("Package '{package}' not found")]
91 PackageNotFound {
92 /// The package name that was not found.
93 package: String,
94 },
95
96 /// Invalid input parameter.
97 #[error("Invalid input: {0}")]
98 InvalidInput(String),
99
100 /// Empty input provided where a value is required.
101 #[error("Empty {field}: {message}")]
102 EmptyInput {
103 /// The field name that is empty.
104 field: String,
105 /// Detailed message about why the field cannot be empty.
106 message: String,
107 },
108
109 /// Package name contains invalid characters or format.
110 #[error("Invalid package name '{name}': {reason}")]
111 InvalidPackageName {
112 /// The invalid package name.
113 name: String,
114 /// Reason why the package name is invalid.
115 reason: String,
116 },
117
118 /// Search query validation failed.
119 #[error("Invalid search query: {reason}")]
120 InvalidSearchQuery {
121 /// Reason why the search query is invalid.
122 reason: String,
123 },
124
125 /// Input exceeds the maximum byte length.
126 #[error("{field} exceeds maximum length of {max_length} bytes (got {actual_length})")]
127 InputTooLong {
128 /// The field name that is too long.
129 field: String,
130 /// Maximum allowed length in bytes.
131 max_length: usize,
132 /// Actual length of the input in bytes.
133 actual_length: usize,
134 },
135}
136
137#[cfg(feature = "aur")]
138impl ArchToolkitError {
139 /// What: Create a `SearchFailed` error with query context.
140 ///
141 /// Inputs:
142 /// - `query`: The search query that failed
143 /// - `source`: The underlying network error
144 ///
145 /// Output:
146 /// - `ArchToolkitError::SearchFailed` variant
147 ///
148 /// Details:
149 /// - Convenience constructor for search operation errors
150 /// - Preserves both the query and the underlying error
151 #[must_use]
152 pub fn search_failed(query: impl Into<String>, source: reqwest::Error) -> Self {
153 Self::SearchFailed {
154 query: query.into(),
155 source,
156 }
157 }
158
159 /// What: Create an `InfoFailed` error with package names context.
160 ///
161 /// Inputs:
162 /// - `packages`: Slice of package names that failed
163 /// - `source`: The underlying network error
164 ///
165 /// Output:
166 /// - `ArchToolkitError::InfoFailed` variant
167 ///
168 /// Details:
169 /// - Convenience constructor for info operation errors
170 /// - Formats package names as comma-separated string
171 /// - Preserves both the package names and the underlying error
172 #[must_use]
173 pub fn info_failed(packages: &[&str], source: reqwest::Error) -> Self {
174 Self::InfoFailed {
175 packages: packages.join(", "),
176 source,
177 }
178 }
179
180 /// What: Create a `CommentsFailed` error with package name context.
181 ///
182 /// Inputs:
183 /// - `package`: The package name that failed
184 /// - `source`: The underlying network error
185 ///
186 /// Output:
187 /// - `ArchToolkitError::CommentsFailed` variant
188 ///
189 /// Details:
190 /// - Convenience constructor for comments operation errors
191 /// - Preserves both the package name and the underlying error
192 #[must_use]
193 pub fn comments_failed(package: impl Into<String>, source: reqwest::Error) -> Self {
194 Self::CommentsFailed {
195 package: package.into(),
196 source,
197 }
198 }
199
200 /// What: Create a `PkgbuildFailed` error with package name context.
201 ///
202 /// Inputs:
203 /// - `package`: The package name that failed
204 /// - `source`: The underlying network error
205 ///
206 /// Output:
207 /// - `ArchToolkitError::PkgbuildFailed` variant
208 ///
209 /// Details:
210 /// - Convenience constructor for pkgbuild operation errors
211 /// - Preserves both the package name and the underlying error
212 #[must_use]
213 pub fn pkgbuild_failed(package: impl Into<String>, source: reqwest::Error) -> Self {
214 Self::PkgbuildFailed {
215 package: package.into(),
216 source,
217 }
218 }
219}
220
221impl ArchToolkitError {
222 /// What: Create an `Io` error with file path context.
223 ///
224 /// Inputs:
225 /// - `path`: The file path where the I/O operation failed
226 /// - `source`: The underlying I/O error
227 ///
228 /// Output:
229 /// - `ArchToolkitError::Io` variant
230 ///
231 /// Details:
232 /// - Convenience constructor for file I/O errors
233 /// - Preserves both the path and the underlying error
234 #[must_use]
235 pub fn io(path: impl Into<String>, source: std::io::Error) -> Self {
236 Self::Io {
237 path: path.into(),
238 source,
239 }
240 }
241}
242
243/// Result type alias for arch-toolkit operations.
244pub type Result<T> = std::result::Result<T, ArchToolkitError>;