npm_package/
lib.rs

1use reqwest::blocking::Client as SyncClient;
2use reqwest::Client as AsyncClient;
3use serde::Deserialize;
4use std::collections::HashMap;
5
6const NPM_REGISTRY_URL: &str = "https://registry.npmjs.org";
7
8#[derive(Debug, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub struct NpmPackage {
11    pub _id: String,
12    pub _rev: String,
13    pub name: String,
14    pub description: String,
15    #[serde(rename = "dist-tags")]
16    pub dist_tags: HashMap<String, String>,
17    pub versions: HashMap<String, NpmPackageVersion>,
18    pub time: HashMap<String, String>,
19    pub author: NpmPackageAuthor,
20    pub repository: Option<NpmPackageRepository>,
21    pub readme: Option<String>,
22}
23
24#[derive(Debug, Deserialize)]
25pub struct NpmPackageVersion {
26    pub name: String,
27    pub version: String,
28    pub homepage: String,
29    pub repository: Option<NpmPackageRepository>,
30    pub dependencies: Option<HashMap<String, String>>,
31    #[serde(alias = "devDependencies")]
32    pub dev_dependencies: Option<HashMap<String, String>>,
33    pub scripts: HashMap<String, String>,
34    pub author: NpmPackageAuthor,
35    pub license: Option<String>,
36    pub readme: Option<String>,
37    #[serde(alias = "readmeFilename")]
38    pub readme_filename: Option<String>,
39    pub _id: String,
40    pub description: String,
41    pub dist: NpmPackageVersionDist,
42    #[serde(alias = "_npmVersion")]
43    pub _npm_version: Option<String>,
44    #[serde(alias = "_npmUser")]
45    pub _npm_user: Option<NpmPackageAuthor>,
46    pub maintainers: Vec<NpmPackageAuthor>,
47}
48
49#[derive(Debug, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub struct NpmPackageVersionDist {
52    pub shasum: String,
53    pub tarball: String,
54}
55
56#[derive(Debug, Deserialize)]
57pub struct NpmPackageRepository {
58    pub r#type: String,
59    pub url: String,
60}
61
62#[derive(Debug, Deserialize)]
63pub struct NpmPackageAuthor {
64    pub name: Option<String>,
65    pub email: Option<String>,
66    pub url: Option<String>,
67}
68
69#[derive(Debug)]
70pub struct SyncNpmClient {
71    pub client: SyncClient,
72}
73
74impl SyncNpmClient {
75    pub fn new() -> Self {
76        Self {
77            client: SyncClient::new(),
78        }
79    }
80
81    pub fn get(&self, package_name: &str) -> Result<NpmPackage, reqwest::Error> {
82        let package_url = format!("{}/{}", NPM_REGISTRY_URL, package_name);
83
84        let response = self.client.get(&package_url).send()?;
85        let package: NpmPackage = response.json()?;
86
87        Ok(package)
88    }
89}
90
91pub struct AsyncNpmClient {
92    pub client: AsyncClient,
93}
94
95impl AsyncNpmClient {
96    pub fn new() -> Self {
97        Self {
98            client: AsyncClient::new(),
99        }
100    }
101
102    pub async fn get(&self, package_name: &str) -> Result<NpmPackage, reqwest::Error> {
103        let package_url = format!("{}/{}", NPM_REGISTRY_URL, package_name);
104
105        let response = self.client.get(&package_url).send().await?;
106        let package: NpmPackage = response.json().await?;
107
108        Ok(package)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use crate::SyncNpmClient;
115
116    #[test]
117    fn test_sync_get_name() {
118        let client = SyncNpmClient::new();
119        let package = client.get("webpack").unwrap();
120
121        assert_eq!(package.name, "webpack");
122    }
123
124    #[test]
125    fn test_sync_getting_a_bad_package_name() {
126        let client = SyncNpmClient::new();
127        let package = client.get("webpacksadfas");
128
129        assert!(package.is_err());
130    }
131
132    #[tokio::test]
133    async fn test_async_get_name() {
134        let client = crate::AsyncNpmClient::new();
135        let package = client.get("webpack").await.unwrap();
136
137        assert_eq!(package.name, "webpack");
138    }
139
140    #[tokio::test]
141    async fn test_async_getting_a_bad_package_name() {
142        let client = crate::AsyncNpmClient::new();
143        let package = client.get("webpacksadfas").await;
144
145        assert!(package.is_err());
146    }
147
148    #[test]
149    fn test_npm_version_field_serde() {
150        let client = SyncNpmClient::new();
151        let package = client.get("webpack").unwrap();
152
153        assert_eq!(
154            package.versions["5.0.0"]._npm_version,
155            Some("6.14.8".to_string())
156        );
157    }
158
159    #[test]
160    fn test_readme_version_filename_return_none_serde() {
161        let client = SyncNpmClient::new();
162        let package = client.get("webpack").unwrap();
163
164        assert_eq!(package.versions["5.0.0"].readme_filename, None);
165    }
166}