Skip to main content

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>;