npm-package 0.2.0

A simple client for fetching metadata from the npm package
Documentation
use reqwest::blocking::Client as SyncClient;
use reqwest::Client as AsyncClient;
use serde::Deserialize;
use std::collections::HashMap;

const NPM_REGISTRY_URL: &str = "https://registry.npmjs.org";

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct NpmPackage {
    pub _id: String,
    pub _rev: String,
    pub name: String,
    pub description: String,
    #[serde(rename = "dist-tags")]
    pub dist_tags: HashMap<String, String>,
    pub versions: HashMap<String, NpmPackageVersion>,
    pub time: HashMap<String, String>,
    pub author: NpmPackageAuthor,
    pub repository: Option<NpmPackageRepository>,
    pub readme: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct NpmPackageVersion {
    pub name: String,
    pub version: String,
    pub homepage: String,
    pub repository: Option<NpmPackageRepository>,
    pub dependencies: Option<HashMap<String, String>>,
    #[serde(alias = "devDependencies")]
    pub dev_dependencies: Option<HashMap<String, String>>,
    pub scripts: HashMap<String, String>,
    pub author: NpmPackageAuthor,
    pub license: Option<String>,
    pub readme: Option<String>,
    #[serde(alias = "readmeFilename")]
    pub readme_filename: Option<String>,
    pub _id: String,
    pub description: String,
    pub dist: NpmPackageVersionDist,
    #[serde(alias = "_npmVersion")]
    pub _npm_version: Option<String>,
    #[serde(alias = "_npmUser")]
    pub _npm_user: Option<NpmPackageAuthor>,
    pub maintainers: Vec<NpmPackageAuthor>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct NpmPackageVersionDist {
    pub shasum: String,
    pub tarball: String,
}

#[derive(Debug, Deserialize)]
pub struct NpmPackageRepository {
    pub r#type: String,
    pub url: String,
}

#[derive(Debug, Deserialize)]
pub struct NpmPackageAuthor {
    pub name: Option<String>,
    pub email: Option<String>,
    pub url: Option<String>,
}

#[derive(Debug)]
pub struct SyncNpmClient {
    pub client: SyncClient,
}

impl SyncNpmClient {
    pub fn new() -> Self {
        Self {
            client: SyncClient::new(),
        }
    }

    pub fn get(&self, package_name: &str) -> Result<NpmPackage, reqwest::Error> {
        let package_url = format!("{}/{}", NPM_REGISTRY_URL, package_name);

        let response = self.client.get(&package_url).send()?;
        let package: NpmPackage = response.json()?;

        Ok(package)
    }
}

pub struct AsyncNpmClient {
    pub client: AsyncClient,
}

impl AsyncNpmClient {
    pub fn new() -> Self {
        Self {
            client: AsyncClient::new(),
        }
    }

    pub async fn get(&self, package_name: &str) -> Result<NpmPackage, reqwest::Error> {
        let package_url = format!("{}/{}", NPM_REGISTRY_URL, package_name);

        let response = self.client.get(&package_url).send().await?;
        let package: NpmPackage = response.json().await?;

        Ok(package)
    }
}

#[cfg(test)]
mod tests {
    use crate::SyncNpmClient;

    #[test]
    fn test_sync_get_name() {
        let client = SyncNpmClient::new();
        let package = client.get("webpack").unwrap();

        assert_eq!(package.name, "webpack");
    }

    #[test]
    fn test_sync_getting_a_bad_package_name() {
        let client = SyncNpmClient::new();
        let package = client.get("webpacksadfas");

        assert!(package.is_err());
    }

    #[tokio::test]
    async fn test_async_get_name() {
        let client = crate::AsyncNpmClient::new();
        let package = client.get("webpack").await.unwrap();

        assert_eq!(package.name, "webpack");
    }

    #[tokio::test]
    async fn test_async_getting_a_bad_package_name() {
        let client = crate::AsyncNpmClient::new();
        let package = client.get("webpacksadfas").await;

        assert!(package.is_err());
    }

    #[test]
    fn test_npm_version_field_serde() {
        let client = SyncNpmClient::new();
        let package = client.get("webpack").unwrap();

        assert_eq!(
            package.versions["5.0.0"]._npm_version,
            Some("6.14.8".to_string())
        );
    }

    #[test]
    fn test_readme_version_filename_return_none_serde() {
        let client = SyncNpmClient::new();
        let package = client.get("webpack").unwrap();

        assert_eq!(package.versions["5.0.0"].readme_filename, None);
    }
}