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}