krsk 0.1.2

krsk is very cool tool for crates.io integration
Documentation
#![allow(dead_code)]

use serde::{Deserialize, Serialize};
use serde_json::Value;

const API_URL: &str = "https://crates.io/api/v1/crates";

#[derive(Deserialize, Serialize, Debug)]
pub struct Crateio {
    #[serde(rename = "crate")]
    pub crate_data: CrateData,
    pub keywords: Option<Vec<Value>>,
    pub versions: Vec<Value>,
}
impl Crateio {
    pub async fn get(name: &str) -> Result<Self, reqwest::Error> {
        let url = format!("{API_URL}/{name}");
        let res = reqwest::Client::new()
            .get(url)
            .header("User-Agent", "krsk/0.1.0 ya@example.com")
            .send()
            .await?;

        res.json::<crate::Crateio>().await
    }
    pub async fn get_last_features(&self) -> Option<Vec<String>> {
        self.get_features(0).await
    }
    pub async fn get_features(&self, id: u64) -> Option<Vec<String>> {
        if let Some(version) = self.get_version(id).await {
            return version.get_features().await;
        }
        None
    }
    pub async fn get_data(&self) -> &CrateData {
        &self.crate_data
    }
    pub async fn get_all_versions(&self) -> Vec<Version> {
        let mut vs = vec![];
        for v in self.versions.iter() {
            if let Ok(v) = serde_json::from_value::<Version>(v.clone()) {
                vs.push(v)
            }
        }
        vs
    }
    pub async fn get_version(&self, id: u64) -> Option<Version> {
        if let Some(value) = self.versions.get(id as usize) {
            if let Ok(version) = serde_json::from_value(value.clone()) {
                return Some(version);
            }
        }
        None
    }
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CrateData {
    categories: Option<Vec<Value>>,
    pub created_at: String,
    pub description: Option<String>,
    pub documentation: Option<String>,
    pub downloads: u64,
    pub exact_match: bool,
    pub homepage: Option<String>,
    pub max_stable_version: Option<String>,
    pub max_version: Option<String>,
    pub name: String,
    pub recent_downloads: u64,
    pub repository: Option<String>,
    pub updated_at: String,
}
impl CrateData {
    pub async fn get_categories(&self) -> Option<Vec<String>> {
        self.categories.as_ref().map(|categories| {
            categories
                .iter()
                .map(|c| serde_json::from_value(c.clone()).unwrap_or(String::new()))
                .collect()
        })
    }
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Version {
    pub checksum: String,
    #[serde(rename = "crate")]
    pub crate_name: String,
    pub crate_size: Option<u64>,
    pub created_at: String,
    pub downloads: u64,
    features: Option<Value>,
    pub has_lib: bool,
    pub id: u64,
    pub license: Option<String>,
    pub num: String,
    pub published_by: Author,
    pub rust_version: Option<String>,
    pub updated_at: String,
    pub yanked: bool,
}
impl Version {
    pub async fn get_features(&self) -> Option<Vec<String>> {
        if let Some(features) = &self.features {
            return Some(features.as_object().unwrap().keys().cloned().collect());
        }
        None
    }
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Author {
    pub avatar: Option<String>,
    pub id: u64,
    pub login: String,
    pub name: String,
    pub url: String,
}

#[cfg(test)]
mod tests {

    #[tokio::test]
    async fn test_get() {
        let res = super::Crateio::get("tokio").await;
        assert!(res.is_ok());
        let res = super::Crateio::get("aslkdjflsdkjflskdjflskj").await;
        assert!(res.is_err())
    }

    #[tokio::test]
    async fn test_get_last_features() {
        let res = super::Crateio::get("tokio").await.unwrap();
        let features = res.get_last_features().await;
        println!("{:?}", features);
        assert!(features.is_some())
    }

    #[tokio::test]
    async fn test_get_features() {
        let res = super::Crateio::get("tokio").await.unwrap();
        let features = res.get_features(10).await;
        println!("{:?}", features);
        assert!(features.is_some());
        let features = res.get_features(0).await;
        println!("{:?}", features);
        assert!(features.is_some());
        let features = res.get_features(10000000).await;
        assert!(features.is_none());
    }

    #[tokio::test]
    async fn test_get_data() {
        let res = super::Crateio::get("tokio").await.unwrap();
        let data = res.get_data().await;
        println!("{:?}", data);
    }

    #[tokio::test]
    async fn test_get_all_versions() {
        let res = super::Crateio::get("tokio").await.unwrap();
        let versions = res.get_all_versions().await;
        assert!(!versions.is_empty());
    }

    #[tokio::test]
    async fn test_get_version() {
        let res = super::Crateio::get("tokio").await.unwrap();
        let version = res.get_version(10).await;
        println!("{:?}", version);
        assert!(version.is_some());
        let version = res.get_version(1).await;
        println!("{:?}", version);
        assert!(version.is_some());
        let version = res.get_version(10000).await;
        println!("{:?}", version);
        assert!(version.is_none());
    }

    #[tokio::test]
    async fn test_get_categories() {
        let res = super::Crateio::get("tokio").await.unwrap();
        let cs = res.get_data().await.get_categories().await;
        assert!(cs.clone().is_some());
        assert!(cs.unwrap().len() == 2)
    }
}