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    #[error("Network error: {0}")]
17    Network(reqwest::Error),
18
19    /// AUR search operation failed.
20    #[error("AUR search failed for query '{query}': {source}")]
21    SearchFailed {
22        /// The search query that failed.
23        query: String,
24        /// The underlying network error.
25        #[source]
26        source: reqwest::Error,
27    },
28
29    /// AUR info fetch operation failed.
30    #[error("AUR info fetch failed for packages [{packages}]: {source}")]
31    InfoFailed {
32        /// Comma-separated list of package names that failed.
33        packages: String,
34        /// The underlying network error.
35        #[source]
36        source: reqwest::Error,
37    },
38
39    /// AUR comments fetch operation failed.
40    #[error("AUR comments fetch failed for package '{package}': {source}")]
41    CommentsFailed {
42        /// The package name that failed.
43        package: String,
44        /// The underlying network error.
45        #[source]
46        source: reqwest::Error,
47    },
48
49    /// PKGBUILD fetch operation failed.
50    #[error("PKGBUILD fetch failed for package '{package}': {source}")]
51    PkgbuildFailed {
52        /// The package name that failed.
53        package: String,
54        /// The underlying network error.
55        #[source]
56        source: reqwest::Error,
57    },
58
59    /// JSON parsing error.
60    #[error("JSON parsing error: {0}")]
61    Json(#[from] serde_json::Error),
62
63    /// Custom parsing error with message.
64    #[error("Parse error: {0}")]
65    Parse(String),
66
67    /// Rate limiting error with optional retry-after information.
68    #[error("Rate limited by server{0}", .retry_after.map(|s| format!(" (retry after {s}s)")).unwrap_or_default())]
69    RateLimited {
70        /// Optional retry-after value in seconds from server.
71        retry_after: Option<u64>,
72    },
73
74    /// Package not found (enhanced with package name).
75    #[error("Package '{package}' not found")]
76    PackageNotFound {
77        /// The package name that was not found.
78        package: String,
79    },
80
81    /// Invalid input parameter.
82    #[error("Invalid input: {0}")]
83    InvalidInput(String),
84
85    /// Empty input provided where a value is required.
86    #[error("Empty {field}: {message}")]
87    EmptyInput {
88        /// The field name that is empty.
89        field: String,
90        /// Detailed message about why the field cannot be empty.
91        message: String,
92    },
93
94    /// Package name contains invalid characters or format.
95    #[error("Invalid package name '{name}': {reason}")]
96    InvalidPackageName {
97        /// The invalid package name.
98        name: String,
99        /// Reason why the package name is invalid.
100        reason: String,
101    },
102
103    /// Search query validation failed.
104    #[error("Invalid search query: {reason}")]
105    InvalidSearchQuery {
106        /// Reason why the search query is invalid.
107        reason: String,
108    },
109
110    /// Input exceeds maximum length.
111    #[error("{field} exceeds maximum length of {max_length} characters (got {actual_length})")]
112    InputTooLong {
113        /// The field name that is too long.
114        field: String,
115        /// Maximum allowed length.
116        max_length: usize,
117        /// Actual length of the input.
118        actual_length: usize,
119    },
120}
121
122impl ArchToolkitError {
123    /// What: Create a `SearchFailed` error with query context.
124    ///
125    /// Inputs:
126    /// - `query`: The search query that failed
127    /// - `source`: The underlying network error
128    ///
129    /// Output:
130    /// - `ArchToolkitError::SearchFailed` variant
131    ///
132    /// Details:
133    /// - Convenience constructor for search operation errors
134    /// - Preserves both the query and the underlying error
135    #[must_use]
136    pub fn search_failed(query: impl Into<String>, source: reqwest::Error) -> Self {
137        Self::SearchFailed {
138            query: query.into(),
139            source,
140        }
141    }
142
143    /// What: Create an `InfoFailed` error with package names context.
144    ///
145    /// Inputs:
146    /// - `packages`: Slice of package names that failed
147    /// - `source`: The underlying network error
148    ///
149    /// Output:
150    /// - `ArchToolkitError::InfoFailed` variant
151    ///
152    /// Details:
153    /// - Convenience constructor for info operation errors
154    /// - Formats package names as comma-separated string
155    /// - Preserves both the package names and the underlying error
156    #[must_use]
157    pub fn info_failed(packages: &[&str], source: reqwest::Error) -> Self {
158        Self::InfoFailed {
159            packages: packages.join(", "),
160            source,
161        }
162    }
163
164    /// What: Create a `CommentsFailed` error with package name context.
165    ///
166    /// Inputs:
167    /// - `package`: The package name that failed
168    /// - `source`: The underlying network error
169    ///
170    /// Output:
171    /// - `ArchToolkitError::CommentsFailed` variant
172    ///
173    /// Details:
174    /// - Convenience constructor for comments operation errors
175    /// - Preserves both the package name and the underlying error
176    #[must_use]
177    pub fn comments_failed(package: impl Into<String>, source: reqwest::Error) -> Self {
178        Self::CommentsFailed {
179            package: package.into(),
180            source,
181        }
182    }
183
184    /// What: Create a `PkgbuildFailed` error with package name context.
185    ///
186    /// Inputs:
187    /// - `package`: The package name that failed
188    /// - `source`: The underlying network error
189    ///
190    /// Output:
191    /// - `ArchToolkitError::PkgbuildFailed` variant
192    ///
193    /// Details:
194    /// - Convenience constructor for pkgbuild operation errors
195    /// - Preserves both the package name and the underlying error
196    #[must_use]
197    pub fn pkgbuild_failed(package: impl Into<String>, source: reqwest::Error) -> Self {
198        Self::PkgbuildFailed {
199            package: package.into(),
200            source,
201        }
202    }
203}
204
205/// Result type alias for arch-toolkit operations.
206pub type Result<T> = std::result::Result<T, ArchToolkitError>;