use crate::{
Certainty, GuesserSettings, Person, ProviderError, UpstreamDatum, UpstreamDatumWithMetadata,
};
use log::error;
use std::path::Path;
use url::Url;
pub fn guess_from_package_json(
path: &Path,
_settings: &GuesserSettings,
) -> std::result::Result<Vec<UpstreamDatumWithMetadata>, ProviderError> {
let file = std::fs::File::open(path)?;
let package: serde_json::Value =
serde_json::from_reader(file).map_err(|e| ProviderError::ParseError(e.to_string()))?;
let mut upstream_data: Vec<UpstreamDatumWithMetadata> = Vec::new();
let package = match package {
serde_json::Value::Object(package) => package,
_ => {
return Err(ProviderError::ParseError(
"package.json is not an object".to_string(),
));
}
};
for (field, value) in package {
match field.as_str() {
"name" => {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Name(value.as_str().unwrap().to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
"homepage" => {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Homepage(value.as_str().unwrap().to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
"description" => {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Summary(value.as_str().unwrap().to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
"license" => {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::License(value.as_str().unwrap().to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
"demo" => {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Demo(value.as_str().unwrap().to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
"version" => {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Version(value.as_str().unwrap().to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
"repository" => {
let repo_url = if let Some(repo_url) = value.as_str() {
Some(repo_url)
} else if let Some(repo) = value.as_object() {
if let Some(repo_url) = repo.get("url") {
repo_url.as_str()
} else {
None
}
} else {
None
};
if let Some(repo_url) = repo_url {
match Url::parse(repo_url) {
Ok(url) if url.scheme() == "github" => {
let repo_url = format!("https://github.com/{}", url.path());
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Repository(repo_url.to_string()),
certainty: Some(Certainty::Likely),
origin: Some(path.into()),
});
}
Err(url::ParseError::RelativeUrlWithoutBase) => {
let repo_url = format!("https://github.com/{}", repo_url);
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Repository(repo_url.to_string()),
certainty: Some(Certainty::Likely),
origin: Some(path.into()),
});
}
Ok(url) => {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Repository(url.to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
Err(e) => {
panic!("Failed to parse repository URL: {}", e);
}
}
}
}
"bugs" => {
if let Some(url) = value.as_str() {
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::BugDatabase(url.to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
} else if let Some(email) = value.get("email").and_then(serde_json::Value::as_str) {
let url = format!("mailto:{}", email);
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::BugDatabase(url.to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
"keywords" => {
if let Some(keywords) = value.as_array() {
let keywords = keywords
.iter()
.filter_map(|keyword| keyword.as_str())
.map(String::from)
.collect();
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Keywords(keywords),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
"author" => {
if let Some(author) = value.as_object() {
let name = author
.get("name")
.and_then(serde_json::Value::as_str)
.map(String::from);
let url = author
.get("url")
.and_then(serde_json::Value::as_str)
.map(String::from);
let email = author
.get("email")
.and_then(serde_json::Value::as_str)
.map(String::from);
let person = Person { name, url, email };
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Author(vec![person]),
certainty: Some(Certainty::Confident),
origin: Some(path.into()),
});
} else if let Some(author) = value.as_str() {
let person = Person::from(author);
upstream_data.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Author(vec![person]),
certainty: Some(Certainty::Confident),
origin: Some(path.into()),
});
} else {
error!("Unsupported type for author in package.json: {:?}", value);
}
}
"dependencies" | "private" | "devDependencies" | "scripts" | "files" | "main" => {
}
_ => {
error!("Unknown package.json field {} ({:?})", field, value);
}
}
}
Ok(upstream_data)
}
#[cfg(test)]
mod package_json_tests {
use super::*;
#[test]
fn test_dummy() {
let td = tempfile::tempdir().unwrap();
let path = td.path().join("package.json");
std::fs::write(
&path,
r#"{
"name": "mozillaeslintsetup",
"description": "This package file is for setup of ESLint.",
"repository": {},
"license": "MPL-2.0",
"dependencies": {
"eslint": "4.18.1",
"eslint-plugin-html": "4.0.2",
"eslint-plugin-mozilla": "file:tools/lint/eslint/eslint-plugin-mozilla",
"eslint-plugin-no-unsanitized": "2.0.2",
"eslint-plugin-react": "7.1.0",
"eslint-plugin-spidermonkey-js":
"file:tools/lint/eslint/eslint-plugin-spidermonkey-js"
},
"devDependencies": {}
}
"#,
)
.unwrap();
let ret = guess_from_package_json(&path, &GuesserSettings::default()).unwrap();
assert_eq!(
ret,
vec![
UpstreamDatumWithMetadata {
datum: UpstreamDatum::Summary(
"This package file is for setup of ESLint.".to_string()
),
certainty: Some(Certainty::Certain),
origin: Some(path.clone().into()),
},
UpstreamDatumWithMetadata {
datum: UpstreamDatum::License("MPL-2.0".to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.clone().into())
},
UpstreamDatumWithMetadata {
datum: UpstreamDatum::Name("mozillaeslintsetup".to_string()),
certainty: Some(Certainty::Certain),
origin: Some(path.into())
}
]
);
}
}