1use 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 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 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}