#[macro_use]
extern crate serde;
pub mod api;
mod query;
pub use query::{Category, Query, Sorting};
use std::sync::Mutex;
use std::time::{Duration, Instant};
use anyhow::{Error, Result};
use http_req::request::Request;
use http_req::uri::Uri;
use crate::api::{Authors, Categories, Crate, Dependencies, Downloads, Keywords, Owners, Summary};
use api::{Crates, Version};
use serde::de::DeserializeOwned;
const BASE_URL: &'static str = "https://crates.io/api/v1/";
const RATE_LIMIT: Duration = Duration::from_secs(1);
pub struct Client {
base_url: String,
user_agent: String,
last_request: Mutex<Instant>,
}
impl Client {
pub fn new(user_agent: &str) -> Self {
Self::new_with_base_url(BASE_URL, user_agent)
}
pub fn new_with_base_url(base_url: &str, user_agent: &str) -> Self {
Self {
base_url: base_url.to_string(),
user_agent: user_agent.to_string(),
last_request: Mutex::new(Instant::now() - RATE_LIMIT),
}
}
fn url_crates(&self, query: Query) -> Result<String> {
let mut url = self.base_url.clone();
url.push_str("crates?");
if let Some(page) = query.page {
url.push_str(&format!("page={}", page));
}
if let Some(per_page) = query.per_page {
url.push_str(&format!("&per_page={}", per_page));
}
if let Some(sort) = query.sort {
url.push_str(&format!("&sort={}", sort.to_str()));
}
if let Some(query_string) = query.string {
url.push_str(&format!("&q={}", query_string));
}
if let Some(cat) = query.category {
url.push_str(&format!("&category={}", cat.to_str()));
}
if let Some(keyword) = query.keyword {
url.push_str(&format!("&keyword={}", keyword))
}
Ok(url)
}
pub fn get_crates(&self, query: Query) -> Result<Crates> {
let crates = self.get(&self.url_crates(query)?)?;
Ok(crates)
}
pub fn try_get_crates(&self, query: Query) -> Result<Crates> {
let crates = self.try_get(&self.url_crates(query)?)?;
Ok(crates)
}
fn url_crate(&self, crate_id: &str) -> Result<String> {
Ok(format!("{}crates/{}", self.base_url, crate_id))
}
pub fn get_crate(&self, crate_id: &str) -> Result<Crate> {
let crate_ = self.get(&self.url_crate(crate_id)?)?;
Ok(crate_)
}
pub fn try_get_crate(&self, crate_id: &str) -> Result<Crate> {
let crate_ = self.try_get(&self.url_crate(crate_id)?)?;
Ok(crate_)
}
fn url_crate_version(&self, crate_id: &str, crate_version: &str) -> Result<String> {
Ok(format!(
"{}crates/{}/{}",
self.base_url, crate_id, crate_version
))
}
pub fn get_crate_version(&self, crate_id: &str, crate_version: &str) -> Result<Version> {
let version = self.get(&self.url_crate_version(crate_id, crate_version)?)?;
Ok(version)
}
pub fn try_get_crate_version(&self, crate_id: &str, crate_version: &str) -> Result<Version> {
let version = self.try_get(&self.url_crate_version(crate_id, crate_version)?)?;
Ok(version)
}
fn url_crate_downloads(&self, crate_id: &str) -> Result<String> {
Ok(format!("{}crates/{}/downloads", self.base_url, crate_id))
}
pub fn get_crate_downloads(&self, crate_id: &str) -> Result<Downloads> {
let downloads = self.get(&self.url_crate_downloads(crate_id)?)?;
Ok(downloads)
}
pub fn try_get_crate_downloads(&self, crate_id: &str) -> Result<Downloads> {
let downloads = self.try_get(&self.url_crate_downloads(crate_id)?)?;
Ok(downloads)
}
fn url_crate_dependencies(&self, crate_id: &str, crate_version: &str) -> Result<String> {
Ok(format!(
"{}crates/{}/{}/dependencies",
self.base_url, crate_id, crate_version
))
}
pub fn get_crate_dependencies(
&self,
crate_id: &str,
crate_version: &str,
) -> Result<Dependencies> {
let dependencies = self.get(&self.url_crate_dependencies(crate_id, crate_version)?)?;
Ok(dependencies)
}
pub fn try_get_crate_dependencies(
&self,
crate_id: &str,
crate_version: &str,
) -> Result<Dependencies> {
let dependencies = self.get(&self.url_crate_dependencies(crate_id, crate_version)?)?;
Ok(dependencies)
}
fn url_crate_owners(&self, crate_id: &str) -> Result<String> {
Ok(format!("{}crates/{}/owners", self.base_url, crate_id))
}
pub fn get_crate_owners(&self, crate_id: &str) -> Result<Owners> {
let owners = self.get(&self.url_crate_owners(crate_id)?)?;
Ok(owners)
}
pub fn try_get_crate_owners(&self, crate_id: &str) -> Result<Owners> {
let owners = self.try_get(&self.url_crate_owners(crate_id)?)?;
Ok(owners)
}
fn url_crate_authors(&self, crate_id: &str, crate_version: &str) -> Result<String> {
Ok(format!(
"{}crates/{}/{}/authors",
self.base_url, crate_id, crate_version
))
}
pub fn get_crate_authors(&self, crate_id: &str, crate_version: &str) -> Result<Authors> {
let authors = self.get(&self.url_crate_authors(crate_id, crate_version)?)?;
Ok(authors)
}
pub fn try_get_crate_authors(&self, crate_id: &str, crate_version: &str) -> Result<Authors> {
let authors = self.try_get(&self.url_crate_authors(crate_id, crate_version)?)?;
Ok(authors)
}
fn url_crate_readme(&self, crate_id: &str, crate_version: &str) -> Result<String> {
Ok(format!(
"{}crates/{}/{}/readme",
self.base_url, crate_id, crate_version
))
}
pub fn get_crate_readme(&self, crate_id: &str, crate_version: &str) -> Result<String> {
let readme = self.get(&self.url_crate_readme(crate_id, crate_version)?)?;
Ok(readme)
}
pub fn try_get_crate_readme(&self, crate_id: &str, crate_version: &str) -> Result<String> {
let readme = self.try_get(&self.url_crate_readme(crate_id, crate_version)?)?;
Ok(readme)
}
fn url_registry_summary(&self) -> Result<String> {
Ok(format!("{}summary", self.base_url))
}
pub fn get_registry_summary(&self) -> Result<Summary> {
let summary = self.get(&self.url_registry_summary()?)?;
Ok(summary)
}
pub fn try_get_registry_summary(&self) -> Result<Summary> {
let summary = self.try_get(&self.url_registry_summary()?)?;
Ok(summary)
}
fn url_category(&self, query: Query) -> Result<String> {
let mut cat_string = None;
if let Some(s) = query.string {
cat_string = Some(s);
} else if let Some(cat) = query.category {
cat_string = Some(cat.to_str().to_string());
}
if let Some(cats) = cat_string {
let url = format!("{}categories/{}", self.base_url, &cats);
Ok(url)
} else {
Err(Error::msg(
"didn't provide either a string or category argument with query",
))
}
}
pub fn get_category(&self, query: Query) -> Result<api::Category> {
let category = self.get(&self.url_category(query)?)?;
Ok(category)
}
pub fn try_get_category(&self, query: Query) -> Result<api::Category> {
let category = self.try_get(&self.url_category(query)?)?;
Ok(category)
}
fn url_categories(&self, query: Query) -> Result<String> {
let mut url = self.base_url.clone();
url.push_str("categories?");
if let Some(page) = query.page {
url.push_str(&format!("page={}", page));
}
if let Some(per_page) = query.per_page {
url.push_str(&format!("&per_page={}", per_page));
}
Ok(url)
}
pub fn get_categories(&self, query: Query) -> Result<Categories> {
let categories = self.get(&self.url_categories(query)?)?;
Ok(categories)
}
pub fn try_get_categories(&self, query: Query) -> Result<Categories> {
let categories = self.try_get(&self.url_categories(query)?)?;
Ok(categories)
}
fn url_keyword(&self, query: Query) -> Result<String> {
let mut key_string = None;
if let Some(s) = query.string {
key_string = Some(s);
} else if let Some(key) = query.keyword {
key_string = Some(key);
}
if let Some(keys) = key_string {
let mut url = self.base_url.clone();
url.push_str(&format!("keywords/{}", &keys));
Ok(url)
} else {
Err(Error::msg(
"didn't provide either a string or keyword argument with query",
))
}
}
pub fn get_keyword(&self, query: Query) -> Result<api::Keyword> {
let keyword = self.get(&self.url_keyword(query)?)?;
Ok(keyword)
}
pub fn try_get_keyword(&self, query: Query) -> Result<api::Keyword> {
let keyword = self.try_get(&self.url_keyword(query)?)?;
Ok(keyword)
}
fn url_keywords(&self, query: Query) -> Result<String> {
let mut url = self.base_url.clone();
url.push_str("keywords?");
if let Some(page) = query.page {
url.push_str(&format!("page={}", page));
}
if let Some(per_page) = query.per_page {
url.push_str(&format!("&per_page={}", per_page));
}
Ok(url)
}
pub fn get_keywords(&self, query: Query) -> Result<Keywords> {
let keywords = self.get(&self.url_keywords(query)?)?;
Ok(keywords)
}
pub fn try_get_keywords(&self, query: Query) -> Result<Keywords> {
let keywords = self.try_get(&self.url_keywords(query)?)?;
Ok(keywords)
}
fn get<T: DeserializeOwned>(&self, url: &str) -> Result<T> {
loop {
match self.try_get(url) {
Err(error) => {
if std::io::ErrorKind::WouldBlock == error.kind() {
std::thread::sleep(Duration::from_millis(60));
continue;
} else {
return Err(Error::from(error));
}
}
Ok(response) => return Ok(response),
}
}
}
fn try_get<T: DeserializeOwned>(&self, url: &str) -> std::io::Result<T> {
let mut lr = self.last_request.lock().unwrap();
if lr.elapsed() >= RATE_LIMIT {
*lr = Instant::now();
} else {
return Err(std::io::Error::new(
std::io::ErrorKind::WouldBlock,
Error::msg("Would block"),
));
}
let mut buffer = Vec::new();
let uri: Uri = url
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let _ = Request::new(&uri)
.header("User-Agent", &self.user_agent)
.send(&mut buffer)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let deser: T = serde_json::from_slice(&buffer)?;
Ok(deser)
}
}