rubygems_api/
lib.rs

1//This file is part of rubygem_api
2//
3//ruygem_api is free software: you can redistribute it and/or modify
4//it under the terms of the GNU General Public License as published by
5//the Free Software Foundation, either version 3 of the License, or
6//(at your option) any later version.
7//
8//rubygem_api is distributed in the hope that it will be useful,
9//but WITHOUT ANY WARRANTY; without even the implied warranty of
10//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11//GNU General Public License for more details.
12//
13//You should have received a copy of the GNU General Public License
14//along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
15
16use log::{debug, info};
17use reqwest::{StatusCode, Url};
18use serde::de::DeserializeOwned;
19use serde_derive::Deserialize;
20use failure::Fail;
21
22#[derive(Fail, Debug)]
23pub enum Error {
24    #[fail(display = "{}", _0)]
25    Http(reqwest::Error),
26    #[fail(display = "{}", _0)]
27    Url(url::ParseError),
28    #[fail(display = "Not found")]
29    NotFound,
30}
31
32impl From<reqwest::Error> for Error {
33    fn from(e: reqwest::Error) -> Self {
34        Error::Http(e)
35    }
36}
37
38impl From<url::ParseError> for Error {
39    fn from(e: url::ParseError) -> Self {
40        Error::Url(e)
41    }
42}
43
44pub struct SyncClient {
45    client: reqwest::Client,
46    base_url: Url,
47}
48
49#[derive(Deserialize, Debug)]
50pub struct GemDevDeps {
51    pub name: String,
52    pub requirements: String,
53}
54
55#[derive(Deserialize, Debug)]
56pub struct GemRunDeps {
57    pub name: String,
58    pub requirements: String,
59}
60
61#[derive(Deserialize, Debug)]
62pub struct GemDeps {
63    pub development: Option<Vec<GemDevDeps>>,
64    pub runtime: Option<Vec<GemRunDeps>>,
65}
66
67#[derive(Deserialize, Debug)]
68pub struct GemInfo {
69    pub name: String,
70    pub authors: String,
71    pub version: String,
72    pub info: Option<String>,
73    pub licenses: Option<Vec<String>>,
74    pub project_uri: String,
75    pub gem_uri: String,
76    pub homepage_uri: Option<String>,
77    pub wiki_uri: Option<String>,
78    pub documentation_uri: Option<String>,
79    pub dependencies: GemDeps,
80    pub sha: String,
81}
82
83impl SyncClient {
84    /// Instantiate a new synchronous API client.
85    ///
86    /// This will fail if the underlying http client could not be created.
87    pub fn new() -> Self {
88        SyncClient {
89            client: reqwest::Client::new(),
90            base_url: Url::parse("https://rubygems.org/api/v1/gems/").unwrap(),
91        }
92    }
93
94    fn get<T: DeserializeOwned>(&self, url: Url) -> Result<T, Error> {
95        info!("GET {}", url);
96
97        let mut res = {
98            let res = self.client.get(url).send()?;
99
100            if res.status() == StatusCode::NOT_FOUND {
101                return Err(Error::NotFound);
102            }
103            res.error_for_status()?
104        };
105
106        let data: T = res.json()?;
107        Ok(data)
108    }
109
110    /// Download all relevant data of a Gem from RubyGems.org
111    ///
112    /// Will fail if either the Gem couldn't be found or querying the API failed
113    pub fn gem_info(&self, name: &str) -> Result<GemInfo, Error> {
114        let url = self.base_url.join(&format!("{}.json", &name))?;
115        let data: GemInfo = self.get(url)?;
116
117        debug!("Received data from API: {:?}", data);
118
119        let deserialized_gemdeps = GemDeps {
120            development: data.dependencies.development,
121            runtime: data.dependencies.runtime,
122        };
123
124        let deserialized_geminfo = GemInfo {
125            name: data.name,
126            version: data.version,
127            authors: data.authors,
128            info: data.info,
129            licenses: data.licenses,
130            project_uri: data.project_uri,
131            gem_uri: data.gem_uri,
132            homepage_uri: data.homepage_uri,
133            wiki_uri: data.wiki_uri,
134            documentation_uri: data.documentation_uri,
135            dependencies: deserialized_gemdeps,
136            sha: data.sha,
137        };
138
139        debug!("GemInfo: {:?}", deserialized_geminfo);
140
141        Ok(deserialized_geminfo)
142    }
143}
144
145#[cfg(test)]
146mod test {
147    use crate::SyncClient;
148
149    #[test]
150    fn test_name() {
151        let client = SyncClient::new();
152        let gem_info = client.gem_info("ruby-json").unwrap();
153        assert!(gem_info.name.len() > 0);
154    }
155
156    #[test]
157    fn test_deps() {
158        let client = SyncClient::new();
159        let gem_info = client.gem_info("ffi").unwrap();
160        let gem_info_deps = gem_info.dependencies.development.unwrap();
161        assert!(gem_info_deps.len() > 0);
162    }
163
164    #[test]
165    fn test_license() {
166        let client = SyncClient::new();
167        let gem_info = client.gem_info("newrelic_rpm").unwrap();
168        println!("{:?}", gem_info.licenses)
169    }
170}