aur_rs/
lib.rs

1//! This is a library for interacting with the Arch User Repository (AUR).
2//! It is a work in progress and is not ready for production use.
3//! The goal is to provide a simple interface for interacting with the AUR.
4//!
5//! For a basic name search call `search_package` which takes a package name
6//! and returns a `SearchResponse` struct.  This struct contains a `results`
7//! field which is a vector of `Package` structs.  These structs contain
8//! information about the package.
9//!
10//! # Example
11//! ```rust
12//! #[tokio::main]
13//! async fn main() {
14//!     let request = aur_rs::Request::default();
15//!     let response = request.search_package_by_name("yay-bin").await.unwrap();
16//!     println!("#{:#?}", response);
17//! }
18//! ```
19//!
20//! Another way to get information about a package is to use the `package_info`
21//! function.  This function takes a package name and returns `ReturnData`.
22//!
23//! # Example
24//! ```rust
25//! #[tokio::main]
26//! async fn main() {
27//! let request = aur_rs::Request::default();
28//!     let response = request.search_info_by_name("yay-bin").await.unwrap();
29//!
30//!     if let Some(keywords) = &response.results[0].keywords {
31//!        println!("Keywords: {:?}", keywords);
32//!    }
33//! }
34//! ```
35
36#![warn(missing_docs)]
37
38use serde::{Deserialize, Serialize};
39use url::Url;
40
41// If we are running tests then use a local mock of the AUR RPC.
42const AUR_RPC_URL: &str = "https://aur.archlinux.org/rpc/v5";
43
44/// This is a request to the AUR RPC
45#[derive(Serialize, Deserialize, Debug)]
46pub struct Request {
47    /// URL of the AUR RPC endpoint.  This is s    assert_eq!(response.results[1].name, "yay-git");t to the value of
48    /// https://aur.archlinux.org/rpc/v5 by default; but for testing,
49    /// or should there be some change this could prove useful.
50    pub endpoint: String,
51}
52
53impl Default for Request {
54    fn default() -> Self {
55        Self {
56            endpoint: AUR_RPC_URL.to_string(),
57        }
58    }
59}
60
61/// This is a a response to a search request.
62#[derive(Serialize, Deserialize, Debug)]
63pub struct ReturnData {
64    /// API version
65    version: u32,
66    #[serde(rename = "type")]
67    /// Response type one of `error`, `search`, `multiinfo``
68    type_: String,
69    /// Number of packages found
70    pub resultcount: u32,
71    /// Vector of packages.  If this is empty then no packages were found,
72    /// or there was an error.
73    pub results: Vec<Package>,
74    /// Error message if there was an error. (probably handle this internally
75    /// for now)
76    error: Option<String>,
77}
78
79/// This is a package.  TODO(2020-05-03): Add more documentation.
80#[derive(Serialize, Deserialize, Debug)]
81pub struct Package {
82    /// Package name
83    #[serde(rename = "Name")]
84    pub name: String,
85    /// Package version
86    #[serde(rename = "Version")]
87    pub version: String,
88    /// Package description (nullable)
89    #[serde(rename = "Description")]
90    pub description: Option<String>,
91    /// Package maintainer (nullable)
92    #[serde(rename = "Maintainer")]
93    pub maintainer: Option<String>,
94    /// Package URL pointing to the package source (nullable)
95    #[serde(rename = "URL")]
96    pub url: Option<String>,
97    /// Number of votes
98    #[serde(rename = "NumVotes")]
99    pub num_votes: u32,
100    /// Popularity of the package from 0 to 10
101    #[serde(rename = "Popularity")]
102    pub popularity: f32,
103    /// Out of date flag.  Could be a date or null.
104    #[serde(rename = "OutOfDate")]
105    pub out_of_date: Option<u32>,
106    /// Package base name.  Usually the same as the package name.
107    #[serde(rename = "PackageBase")]
108    pub package_base: String,
109    /// ID of the package base for this specific package.
110    #[serde(rename = "PackageBaseID")]
111    pub package_base_id: u32,
112    /// First submitted date in unix time.
113    #[serde(rename = "FirstSubmitted")]
114    pub first_submitted: u32,
115    /// Last modified date in unix time.
116    #[serde(rename = "LastModified")]
117    pub last_modified: u32,
118    /// URL path to the package snapshot. (null)
119    #[serde(rename = "URLPath")]
120    pub url_path: Option<String>,
121    /// Unique ID of the package.
122    #[serde(rename = "ID")]
123    pub id: u32,
124
125    // The following fields are optional and only returned when using the
126    // `multiinfo` method.
127    /// Vector of dependencies
128    #[serde(rename = "Depends")]
129    pub depends: Option<Vec<String>>,
130    /// Vector of make dependencies
131    #[serde(rename = "MakeDepends")]
132    pub make_depends: Option<Vec<String>>,
133    /// Vector of optional dependencies
134    #[serde(rename = "OptDepends")]
135    pub opt_depends: Option<Vec<String>>,
136    /// Vector of check dependencies
137    #[serde(rename = "CheckDepends")]
138    pub check_depends: Option<Vec<String>>,
139    /// Vector of conflicts
140    #[serde(rename = "Conflicts")]
141    pub conflicts: Option<Vec<String>>,
142    /// Vector of provides
143    #[serde(rename = "Provides")]
144    pub provides: Option<Vec<String>>,
145    /// Vector of packages replaced by this package
146    #[serde(rename = "Replaces")]
147    pub replaces: Option<Vec<String>>,
148    /// Vector of groups
149    #[serde(rename = "Groups")]
150    pub groups: Option<Vec<String>>,
151    /// Vector of licenses defind in the PKGBUILD
152    #[serde(rename = "License")]
153    pub license: Option<Vec<String>>,
154    /// Vector of keywords
155    #[serde(rename = "Keywords")]
156    pub keywords: Option<Vec<String>>,
157}
158
159impl Request {
160    // Private stuff
161    async fn search(&self, search_term: &str) -> Result<ReturnData, Box<dyn std::error::Error>> {
162        let resp = reqwest::get(search_term).await?.text().await?;
163        let search_response: ReturnData = serde_json::from_str(&resp)?;
164        Ok(search_response)
165    }
166
167    async fn starts_with(&self, url: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
168        let resp = reqwest::get(url).await?.text().await?;
169        let search_response: Vec<String> = serde_json::from_str(&resp)?;
170
171        Ok(search_response)
172    }
173
174    /// Package names are case insensitive.
175    /// They can be searched by a few attributes:
176    /// name, name-desc, depends, checkdepends, optdepends, makedepends,
177    /// maintainer, submitter, provides, conflicts, replaces, keywords,
178    /// groups, and comaintainers
179
180    /// Search for package by package name.
181    pub async fn search_package_by_name(
182        &self,
183        package: &str,
184    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
185        let url = format!("{}/search/{}?by=name", self.endpoint, package);
186        self.search(&url).await
187    }
188
189    /// Search for package by package name and description.
190    pub async fn search_package_by_name_desc(
191        &self,
192        package: &str,
193    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
194        let url = format!("{}/search/{}?by=name-desc", self.endpoint, package);
195        self.search(&url).await
196    }
197
198    /// Search for package that depend on package.
199    pub async fn search_package_by_depends(
200        &self,
201        package: &str,
202    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
203        let url = format!("{}/search/{}?by=depends", self.endpoint, package);
204        self.search(&url).await
205    }
206
207    /// Search for package that check depend on package.
208    pub async fn search_package_by_checkdepends(
209        &self,
210        package: &str,
211    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
212        let url = format!("{}/search/{}?by=checkdepends", self.endpoint, package);
213        self.search(&url).await
214    }
215
216    /// Search for package that optionally depend on package.
217    pub async fn search_package_by_optdepends(
218        &self,
219        package: &str,
220    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
221        let url = format!("{}/search/{}?by=optdepends", self.endpoint, package);
222        self.search(&url).await
223    }
224
225    /// Search for packages that need package to build.
226    pub async fn search_package_by_makedepends(
227        &self,
228        package: &str,
229    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
230        let url = format!("{}/search/{}?by=optdepends", self.endpoint, package);
231        self.search(&url).await
232    }
233
234    /// Search for package by package maintainer.
235    pub async fn search_package_by_maintainer(
236        &self,
237        package: &str,
238    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
239        let url = format!("{}/search/{}?by=maintainer", self.endpoint, package);
240        self.search(&url).await
241    }
242
243    /// Search for package by package submitter.
244    pub async fn search_package_by_submitter(
245        &self,
246        package: &str,
247    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
248        let url = format!("{}/search/{}?by=submitter", self.endpoint, package);
249        self.search(&url).await
250    }
251
252    /// Search for packages that provide the same functionality.
253    pub async fn search_package_by_provides(
254        &self,
255        package: &str,
256    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
257        let url = format!("{}/search/{}?by=provides", self.endpoint, package);
258        self.search(&url).await
259    }
260
261    /// Search for packages that conflict with package.
262    pub async fn search_package_by_conflicts(
263        &self,
264        package: &str,
265    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
266        let url = format!("{}/search/{}?by=conflicts", self.endpoint, package);
267        self.search(&url).await
268    }
269
270    /// Search for packages that this package replaces.
271    pub async fn search_package_by_replaces(
272        &self,
273        package: &str,
274    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
275        let url = format!("{}/search/{}?by=replaces", self.endpoint, package);
276        self.search(&url).await
277    }
278
279    /// Search for packages by keywords.
280    pub async fn search_package_by_keywords(
281        &self,
282        package: &str,
283    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
284        let url = format!("{}/search/{}?by=keywords", self.endpoint, package);
285        self.search(&url).await
286    }
287
288    /// Search packages' groups.
289    pub async fn search_package_by_groups(
290        &self,
291        package: &str,
292    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
293        let url = format!("{}/search/{}?by=groups", self.endpoint, package);
294        self.search(&url).await
295    }
296
297    /// Search for packages by comaintainers.
298    pub async fn search_package_by_comaintainers(
299        &self,
300        package: &str,
301    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
302        let url = format!("{}/search/{}?by=comaintainers", self.endpoint, package);
303        self.search(&url).await
304    }
305
306    /// Search for a packages' info by name.
307    pub async fn search_info_by_name(
308        &self,
309        package: &str,
310    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
311        let url = format!("{}/info/{}", self.endpoint, package);
312        self.search(&url).await
313    }
314
315    /// Search for information on multiple packages at once.
316    pub async fn search_multi_info_by_names(
317        &self,
318        packages: &[&str],
319    ) -> Result<ReturnData, Box<dyn std::error::Error>> {
320        let url_str = format!("{}/info", self.endpoint);
321
322        let mut args: Vec<(&str, &str)> = vec![];
323
324        for package in packages {
325            // Might need to change this to argv%5B%5D rather than arg[] in the
326            // future?
327            args.push(("arg[]", package))
328        }
329
330        let url = Url::parse_with_params(&url_str, args)?;
331
332        return self.search(url.as_str()).await;
333    }
334
335    /// Search for a package that starts with the given string.
336    /// Note: Limited to 20 results.
337    pub async fn starts_with_name(
338        &self,
339        starts_with: &str,
340    ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
341        let url = format!("{}/suggest/{}", self.endpoint, starts_with);
342        self.starts_with(&url).await
343    }
344
345    /// Search for a package base that starts with the given string.
346    /// Note: Limited to 20 results.
347    pub async fn starts_with_basename(
348        &self,
349        base_start: &str,
350    ) -> Result<Vec<String>, Box<dyn std::error::Error>> {
351        let url = format!("{}/suggest-pkgbase/{}", self.endpoint, base_start);
352        self.starts_with(&url).await
353    }
354}