1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#[macro_use]
extern crate display_derive;
extern crate failure;
extern crate reqwest;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use std::cmp::Ordering::Equal;

use failure::{err_msg, Error};
use reqwest::Url;

#[derive(Deserialize, Debug)]
pub struct RaurResult {
    #[serde(rename = "type")]
    response_type: String,

    error: Option<String>,

    #[serde(rename = "results")]
    pub packages: Vec<Package>
}

impl RaurResult {
    pub fn count(&self) -> usize {
        self.packages.len()
    }

    pub fn pop(&mut self) -> Option<Package> {
        self.packages.pop()
    }

    fn sort(&mut self) {
        self.packages.sort_by(
            |a, b| a.popularity.partial_cmp(&b.popularity).unwrap_or(Equal)
        );
    }
}

#[derive(Deserialize, Debug)]
pub struct Package {
    #[serde(rename = "ID")]
    pub id: i32,

    #[serde(rename = "Name")]
    pub name: String,

    #[serde(rename = "PackageBaseID")]
    pub package_base_id: Option<i32>,
    
    #[serde(rename = "PackageBase")]
    pub package_base: Option<String>,

    #[serde(rename = "Version")]
    pub version: String,

    #[serde(rename = "Description")]
    pub description: String,

    /// Package source code url
    #[serde(rename = "URL")]
    pub url: Option<String>,

    #[serde(rename = "NumVotes")]
    pub num_votes: i32,

    #[serde(rename = "Popularity")]
    pub popularity: f32,

    #[serde(rename = "OutOfDate")]
    pub out_of_date: Option<i32>,

    #[serde(rename = "Maintainer")]
    pub maintainer: Option<String>,

    #[serde(rename = "FirstSubmitted")]
    pub first_submitted: Option<i32>,

    #[serde(rename = "LastModified")]
    pub last_modified: Option<i32>,
    
    /// AUR url path. Must be appended to the `aur.archlinux.org` domain
    #[serde(rename = "URLPath")]
    pub url_path: String,
    
    #[serde(rename = "Depends")]
    pub dependencies: Option<Vec<String>>,
    
    #[serde(rename = "MakeDepends")]
    pub make_dependencies: Option<Vec<String>>,
    
    #[serde(rename = "License")]
    pub license: Option<Vec<String>>,
    
    #[serde(rename = "Keywords")]
    pub keywords: Option<Vec<String>>
}

/// Describes a way the AUR should handle a search query
#[derive(Display)]
pub enum SearchStrategy {
    /// search by package name only
    #[display(fmt = "name")]
    Name,

    /// search by package name and description (default)
    #[display(fmt = "name-desc")]
    NameDescription,

    /// search by package maintainer
    #[display(fmt = "maintainer")]
    Maintainer,

    /// search for packages that depend on the query
    #[display(fmt = "depends")]
    Dependency,

    /// search for packages that makedepend on the query
    #[display(fmt = "makedepends")]
    MakeDependency,

    /// search for packages that optdepend on the query
    #[display(fmt = "optdepends")]
    OptionalDependency,

    /// search for packages that checkdepend on the query
    #[display(fmt = "checkdepends")]
    CheckDependency
}

/// Use the AUR RPC `search` functionality. This differs from [`info`](fn.info.html) function in that
/// it will search using a query coupled with a [`SearchStrategy`](enum.SearchStrategy.html)
pub fn search<S: AsRef<str>>(query: S, strategy: SearchStrategy) -> Result<RaurResult, Error> {
    let url = Url::parse_with_params("https://aur.archlinux.org/rpc/",
                                      &[("v", "5"),
                                        ("type", "search"),
                                        ("by", &format!("{}", strategy)),
                                        ("arg", query.as_ref())])?;
    request(url)
}

/// Use the AUR RPC `info` functionality. This differs from the [`search`](fn.search.html)
/// function in that it takes a list (TBA) of exact matches of package names and returns
/// their info.
pub fn info<S: AsRef<str>>(pkg_names: &[S]) -> Result<RaurResult, Error> {
    let mut params = pkg_names
        .iter()
        .map(|name| ("arg[]", name.as_ref()))
        .collect::<Vec<(&str, &str)>>();
    params.extend(&[("v", "5"), ("type", "info")]);
    
    let url = Url::parse_with_params("https://aur.archlinux.org/rpc/", &params)?;
    request(url)
}

fn request(url: Url) -> Result<RaurResult, Error> {
    let mut result: RaurResult = reqwest::get(url)?
        .json()?;

    result.sort();

    if result.response_type == "error" {
        Err(err_msg(result.error.expect("That's Weird. Contact raur developers")))
    } else {
        Ok(result)
    }
}

//TODO: MOAR!
#[cfg(test)]
mod tests {
    use {search, SearchStrategy};

    #[test]
    fn query() {
        let query_res = search(&String::from("zzzzzzzzzzz"), SearchStrategy::NameDescription);

        let result = query_res.unwrap();
        assert_eq!(0, result.count());

        let mut query_res = search(&String::from("spotify"), SearchStrategy::NameDescription).unwrap();
        assert!(query_res.count() > 0);

        let a = query_res.pop().unwrap();
        let b = query_res.pop().unwrap();
        assert!(a.popularity > b.popularity);
    }
}