Skip to main content

arch_toolkit/aur/
mod.rs

1//! AUR (Arch User Repository) operations.
2
3#[cfg(feature = "aur")]
4mod comments;
5#[cfg(feature = "aur")]
6mod info;
7#[cfg(feature = "aur")]
8mod mock;
9#[cfg(all(feature = "aur", feature = "index"))]
10mod official;
11#[cfg(feature = "aur")]
12mod pkgbuild;
13#[cfg(feature = "aur")]
14mod search;
15#[cfg(feature = "aur")]
16mod traits;
17#[cfg(feature = "aur")]
18pub mod utils;
19#[cfg(feature = "aur")]
20pub mod validation;
21
22#[cfg(feature = "aur")]
23use crate::client::ArchClient;
24#[cfg(feature = "aur")]
25use crate::error::Result;
26#[cfg(feature = "aur")]
27use crate::types::{AurComment, AurPackage, AurPackageDetails};
28
29#[cfg(all(feature = "aur", feature = "index"))]
30pub use crate::types::package::{
31    MetadataFetchLimits, MirrorHealth, MirrorHealthLimits, MirrorHealthStatus,
32};
33#[cfg(feature = "aur")]
34pub use mock::MockAurApi;
35#[cfg(all(feature = "aur", feature = "index"))]
36pub use official::{
37    ARCH_PACKAGE_SEARCH_URL, check_mirror_health, fetch_arch_package_detail,
38    fetch_official_package_detail_from,
39};
40#[cfg(feature = "aur")]
41pub use traits::AurApi;
42
43/// What: Wrapper for AUR operations using an `ArchClient`.
44///
45/// Inputs: None (created via `ArchClient::aur()`)
46///
47/// Output: `Aur` instance that provides AUR operation methods
48///
49/// Details:
50/// - Holds a reference to `ArchClient` to access HTTP client and configuration
51/// - Provides methods: `search()`, `info()`, `comments()`, `pkgbuild()`
52/// - All operations use the client's configured timeout and user agent
53/// - Rate limiting is handled automatically
54#[cfg(feature = "aur")]
55#[derive(Debug)]
56pub struct Aur<'a> {
57    /// Reference to the parent `ArchClient`.
58    client: &'a ArchClient,
59}
60
61#[cfg(feature = "aur")]
62impl<'a> Aur<'a> {
63    /// What: Create a new `Aur` wrapper for the given client.
64    ///
65    /// Inputs:
66    /// - `client`: Reference to `ArchClient` to use for operations
67    ///
68    /// Output:
69    /// - `Aur` wrapper instance
70    ///
71    /// Details:
72    /// - Internal constructor, typically called via `ArchClient::aur()`
73    /// - The wrapper uses the client's HTTP client and configuration
74    pub(crate) const fn new(client: &'a ArchClient) -> Self {
75        Self { client }
76    }
77
78    /// What: Search for packages in the AUR by name.
79    ///
80    /// Inputs:
81    /// - `query`: Search query string.
82    ///
83    /// Output:
84    /// - `Result<Vec<AurPackage>>` containing search results, or an error.
85    ///
86    /// Details:
87    /// - Uses AUR RPC v5 search endpoint.
88    /// - Returns the full result array supplied by AUR RPC without assuming an
89    ///   upstream 200-result cap or unsupported pagination parameter.
90    /// - Use [`Aur::search_with_limit`] when the caller needs an explicit,
91    ///   deterministic client-side result cap.
92    /// - Percent-encodes the query string for URL safety.
93    /// - Applies rate limiting for archlinux.org requests.
94    /// - Returns empty vector if no results found (not an error).
95    ///
96    /// # Errors
97    /// - Returns `Err(ArchToolkitError::Network)` if the HTTP request fails
98    /// - Returns `Err(ArchToolkitError::InvalidInput)` if the URL is not from archlinux.org
99    pub async fn search(&self, query: &str) -> Result<Vec<AurPackage>> {
100        search::search(self.client, query).await
101    }
102
103    /// What: Search AUR packages with an explicit caller-selected result cap.
104    ///
105    /// Inputs:
106    /// - `query`: Search query string.
107    /// - `maximum_results`: Non-zero maximum number of returned package rows.
108    ///
109    /// Output:
110    /// - At most `maximum_results` AUR package rows, in the RPC response order.
111    ///
112    /// Details:
113    /// - The cap is applied only after one normal AUR RPC response is fetched;
114    ///   it does not claim server-side pagination or an upstream result limit.
115    /// - [`Aur::search`] remains uncapped so callers can make their own bounded
116    ///   display/storage choice.
117    /// - The client cache retains the complete fetched array, so different
118    ///   caller limits do not make cache contents depend on call order.
119    ///
120    /// # Errors
121    /// - Returns the same validation, network, and parsing errors as [`Aur::search`].
122    pub async fn search_with_limit(
123        &self,
124        query: &str,
125        maximum_results: std::num::NonZeroUsize,
126    ) -> Result<Vec<AurPackage>> {
127        search::search_with_limit(self.client, query, maximum_results).await
128    }
129
130    /// What: Fetch detailed information for one or more AUR packages.
131    ///
132    /// Inputs:
133    /// - `names`: Slice of package names to fetch info for.
134    ///
135    /// Output:
136    /// - `Result<Vec<AurPackageDetails>>` containing package details, or an error.
137    ///
138    /// Details:
139    /// - Uses AUR RPC v5 info endpoint.
140    /// - Fetches info for all packages in a single request (more efficient).
141    /// - Returns empty vector if no packages found (not an error).
142    /// - Applies rate limiting for archlinux.org requests.
143    ///
144    /// # Errors
145    /// - Returns `Err(ArchToolkitError::Network)` if the HTTP request fails
146    /// - Returns `Err(ArchToolkitError::InvalidInput)` if the URL is not from archlinux.org
147    pub async fn info(&self, names: &[&str]) -> Result<Vec<AurPackageDetails>> {
148        info::info(self.client, names).await
149    }
150
151    /// What: Fetch AUR package comments by scraping the AUR package page.
152    ///
153    /// Inputs:
154    /// - `pkgname`: Package name to fetch comments for.
155    ///
156    /// Output:
157    /// - `Result<Vec<AurComment>>` with parsed comments sorted by date (latest first); `Err` on failure.
158    ///
159    /// Details:
160    /// - Fetches HTML from `https://aur.archlinux.org/packages/<pkgname>`
161    /// - Uses `scraper` to parse HTML and extract comment elements
162    /// - Parses dates to Unix timestamps for sorting
163    /// - Sorts comments by date descending (latest first)
164    /// - Handles pinned comments (appear before "Latest Comments" heading)
165    ///
166    /// # Errors
167    /// - Returns `Err(ArchToolkitError::Network)` if the HTTP request fails
168    /// - Returns `Err(ArchToolkitError::InvalidInput)` if the URL is not from archlinux.org
169    /// - Returns `Err(ArchToolkitError::Parse)` if HTML parsing fails
170    pub async fn comments(&self, pkgname: &str) -> Result<Vec<AurComment>> {
171        comments::comments(self.client, pkgname).await
172    }
173
174    /// What: Fetch PKGBUILD content for an AUR package.
175    ///
176    /// Inputs:
177    /// - `package`: Package name to fetch PKGBUILD for.
178    ///
179    /// Output:
180    /// - `Result<String>` with PKGBUILD text when available; `Err` on network or lookup failure.
181    ///
182    /// Details:
183    /// - Fetches from `https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h={package}`
184    /// - Applies rate limiting (200ms minimum interval between requests)
185    /// - Uses timeout from client configuration
186    /// - Returns raw PKGBUILD text
187    ///
188    /// # Errors
189    /// - Returns `Err(ArchToolkitError::Network)` if the HTTP request fails
190    /// - Returns `Err(ArchToolkitError::InvalidInput)` if the URL is not from archlinux.org
191    /// - Returns `Err(ArchToolkitError::Parse)` if rate limiter mutex is poisoned
192    pub async fn pkgbuild(&self, package: &str) -> Result<String> {
193        pkgbuild::pkgbuild(self.client, package).await
194    }
195}
196
197#[cfg(feature = "aur")]
198use async_trait::async_trait;
199
200#[cfg(feature = "aur")]
201#[async_trait]
202impl AurApi for Aur<'_> {
203    /// What: Search for packages in the AUR by name.
204    ///
205    /// Inputs:
206    /// - `query`: Search query string
207    ///
208    /// Output:
209    /// - `Result<Vec<AurPackage>>` containing search results, or an error
210    ///
211    /// Details:
212    /// - Delegates to the underlying search module function
213    async fn search(&self, query: &str) -> Result<Vec<AurPackage>> {
214        search::search(self.client, query).await
215    }
216
217    /// What: Fetch detailed information for one or more AUR packages.
218    ///
219    /// Inputs:
220    /// - `names`: Slice of package names to fetch info for
221    ///
222    /// Output:
223    /// - `Result<Vec<AurPackageDetails>>` containing package details, or an error
224    ///
225    /// Details:
226    /// - Delegates to the underlying info module function
227    async fn info(&self, names: &[&str]) -> Result<Vec<AurPackageDetails>> {
228        info::info(self.client, names).await
229    }
230
231    /// What: Fetch AUR package comments.
232    ///
233    /// Inputs:
234    /// - `pkgname`: Package name to fetch comments for
235    ///
236    /// Output:
237    /// - `Result<Vec<AurComment>>` with parsed comments, or an error
238    ///
239    /// Details:
240    /// - Delegates to the underlying comments module function
241    async fn comments(&self, pkgname: &str) -> Result<Vec<AurComment>> {
242        comments::comments(self.client, pkgname).await
243    }
244
245    /// What: Fetch PKGBUILD content for an AUR package.
246    ///
247    /// Inputs:
248    /// - `package`: Package name to fetch PKGBUILD for
249    ///
250    /// Output:
251    /// - `Result<String>` with PKGBUILD text, or an error
252    ///
253    /// Details:
254    /// - Delegates to the underlying pkgbuild module function
255    async fn pkgbuild(&self, package: &str) -> Result<String> {
256        pkgbuild::pkgbuild(self.client, package).await
257    }
258}