upstream_ontologist/providers/
node.rs1use crate::{ProviderError, UpstreamDatum, UpstreamDatumWithMetadata, UpstreamMetadata};
2use serde::Deserialize;
3use std::collections::HashMap;
4
5#[derive(Deserialize)]
7pub struct NpmVersion {
8 #[serde(rename = "dist")]
10 pub dist: NpmDist,
11 #[serde(rename = "dependencies")]
13 pub dependencies: Option<HashMap<String, String>>,
14 #[serde(rename = "devDependencies")]
16 pub dev_dependencies: Option<HashMap<String, String>>,
17 #[serde(rename = "peerDependencies")]
19 pub peer_dependencies: Option<HashMap<String, String>>,
20 #[serde(rename = "optionalDependencies")]
22 pub optional_dependencies: Option<HashMap<String, String>>,
23 #[serde(rename = "bundledDependencies")]
25 pub bundled_dependencies: Option<Vec<String>>,
26 #[serde(rename = "engines")]
28 pub engines: Option<HashMap<String, String>>,
29 #[serde(rename = "scripts")]
31 pub scripts: Option<HashMap<String, String>>,
32 pub name: String,
34 pub version: String,
36 #[serde(rename = "readmeFilename")]
38 pub readme_filename: Option<String>,
39 #[serde(rename = "maintainers")]
41 pub maintainers: Vec<NpmPerson>,
42 #[serde(rename = "author")]
44 pub author: Option<NpmPerson>,
45 #[serde(rename = "repository")]
47 pub repository: Option<NpmRepository>,
48 #[serde(rename = "bugs")]
50 pub bugs: Option<NpmBugs>,
51 #[serde(rename = "homepage")]
53 pub homepage: Option<String>,
54 #[serde(rename = "keywords")]
56 pub keywords: Option<Vec<String>>,
57 #[serde(rename = "license")]
59 pub license: Option<String>,
60}
61
62#[derive(Deserialize)]
64pub struct NpmPerson {
65 pub name: String,
67 pub email: String,
69}
70
71impl From<NpmPerson> for crate::Person {
72 fn from(person: NpmPerson) -> Self {
73 crate::Person {
74 name: Some(person.name),
75 email: Some(person.email),
76 url: None,
77 }
78 }
79}
80
81#[derive(Deserialize)]
83pub struct NpmDist {
84 pub shasum: String,
86 pub tarball: String,
88 pub integrity: String,
90 pub signatures: Vec<NpmSignature>,
92}
93
94#[derive(Deserialize)]
96pub struct NpmSignature {
97 pub keyid: String,
99 pub sig: String,
101}
102
103#[derive(Deserialize)]
105pub struct NpmRepository {
106 #[serde(rename = "type")]
108 pub type_: String,
109 pub url: String,
111}
112
113#[derive(Deserialize)]
115pub struct NpmBugs {
116 pub url: String,
118}
119
120#[derive(Deserialize)]
122pub struct NpmPackage {
123 #[serde(rename = "_id")]
125 pub id: String,
126 #[serde(rename = "_rev")]
128 pub rev: String,
129 pub name: String,
131 pub description: String,
133 #[serde(rename = "dist-tags")]
135 pub dist_tags: HashMap<String, String>,
136 pub versions: HashMap<String, NpmVersion>,
138 pub readme: String,
140 pub maintainers: Vec<NpmPerson>,
142 pub time: HashMap<String, String>,
144 pub author: Option<NpmPerson>,
146 pub repository: Option<NpmRepository>,
148 pub bugs: Option<NpmBugs>,
150 pub homepage: Option<String>,
152 pub keywords: Option<Vec<String>>,
154 pub license: Option<String>,
156 pub dependencies: Option<HashMap<String, String>>,
158 #[serde(rename = "devDependencies")]
160 pub dev_dependencies: Option<HashMap<String, String>>,
161 #[serde(rename = "peerDependencies")]
163 pub peer_dependencies: Option<HashMap<String, String>>,
164 #[serde(rename = "optionalDependencies")]
166 pub optional_dependencies: Option<HashMap<String, String>>,
167 #[serde(rename = "bundledDependencies")]
169 pub bundled_dependencies: Option<Vec<String>>,
170 pub engines: Option<HashMap<String, String>>,
172 pub scripts: Option<HashMap<String, String>>,
174 #[serde(rename = "readmeFilename")]
176 pub readme_filename: Option<String>,
177}
178
179impl TryInto<UpstreamMetadata> for NpmPackage {
180 type Error = ProviderError;
181
182 fn try_into(self) -> Result<UpstreamMetadata, Self::Error> {
183 let mut metadata = UpstreamMetadata::default();
184
185 let package_name = self.name.clone();
186 metadata.insert(UpstreamDatumWithMetadata {
187 datum: UpstreamDatum::Name(self.name),
188 certainty: None,
189 origin: None,
190 });
191
192 metadata.insert(UpstreamDatumWithMetadata {
193 datum: UpstreamDatum::Description(self.description),
194 certainty: None,
195 origin: None,
196 });
197
198 if let Some(homepage) = self.homepage {
199 metadata.insert(UpstreamDatumWithMetadata {
200 datum: UpstreamDatum::Homepage(homepage),
201 certainty: None,
202 origin: None,
203 });
204 }
205
206 if let Some(author) = self.author {
207 metadata.insert(UpstreamDatumWithMetadata {
208 datum: UpstreamDatum::Author(vec![author.into()]),
209 certainty: None,
210 origin: None,
211 });
212 }
213
214 if let Some(repository) = self.repository {
215 metadata.insert(UpstreamDatumWithMetadata {
216 datum: UpstreamDatum::Repository(repository.url),
217 certainty: None,
218 origin: None,
219 });
220 }
221
222 if let Some(bugs) = self.bugs {
223 metadata.insert(UpstreamDatumWithMetadata {
224 datum: UpstreamDatum::BugDatabase(bugs.url),
225 certainty: None,
226 origin: None,
227 });
228 }
229
230 if let Some(license) = self.license {
231 metadata.insert(UpstreamDatumWithMetadata {
232 datum: UpstreamDatum::License(license),
233 certainty: None,
234 origin: None,
235 });
236 }
237
238 if let Some(keywords) = self.keywords {
239 metadata.insert(UpstreamDatumWithMetadata {
240 datum: UpstreamDatum::Keywords(keywords),
241 certainty: None,
242 origin: None,
243 });
244 }
245
246 if let Some(latest_version) = self.dist_tags.get("latest") {
248 if let Some(version) = self.versions.get(latest_version) {
249 metadata.insert(UpstreamDatumWithMetadata {
250 datum: UpstreamDatum::Version(version.version.to_string()),
251 certainty: None,
252 origin: None,
253 });
254 }
255
256 let version_data = self.versions.get(latest_version).map_or_else(
257 || {
258 Err(ProviderError::Other(format!(
259 "Could not find version {} in package {}",
260 latest_version, &package_name
261 )))
262 },
263 Ok,
264 )?;
265
266 metadata.insert(UpstreamDatumWithMetadata {
267 datum: UpstreamDatum::Download(version_data.dist.tarball.to_string()),
268 certainty: None,
269 origin: None,
270 });
271 }
272
273 Ok(metadata)
274 }
275}
276
277pub async fn load_npm_package(package: &str) -> Result<Option<NpmPackage>, crate::ProviderError> {
282 let http_url: url::Url = format!("https://registry.npmjs.org/{}", package)
283 .parse()
284 .map_err(|e: url::ParseError| crate::ProviderError::Other(e.to_string()))?;
285 let data = crate::load_json_url(&http_url, None).await?;
286 serde_json::from_value(data)
287 .map_err(|e| crate::ProviderError::ParseError(format!("Failed to parse npm data: {}", e)))
288}
289
290pub async fn remote_npm_metadata(package: &str) -> Result<UpstreamMetadata, ProviderError> {
295 let data = load_npm_package(package).await?;
296
297 match data {
298 Some(data) => data.try_into(),
299 None => Ok(UpstreamMetadata::default()),
300 }
301}
302
303#[cfg(test)]
304mod npm_tests {
305 use super::*;
306
307 #[test]
308 fn test_load_npm_package() {
309 let data = include_str!(".././testdata/npm.json");
310
311 let npm_data: NpmPackage = serde_json::from_str(data).unwrap();
312
313 assert_eq!(npm_data.name, "leftpad");
314 }
315}