use crate::xmlparse_simplify_namespaces;
use crate::{Certainty, Person, ProviderError, UpstreamDatum, UpstreamDatumWithMetadata};
use std::path::Path;
pub async fn guess_from_nuspec(
path: &Path,
_trust_package: bool,
) -> std::result::Result<Vec<UpstreamDatumWithMetadata>, ProviderError> {
const NAMESPACES: &[&str] = &["http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"];
let root = match xmlparse_simplify_namespaces(path, NAMESPACES) {
Some(root) => root,
None => {
return Err(crate::ProviderError::ParseError(
"Unable to parse nuspec".to_string(),
));
}
};
if root.name != "package" {
return Err(ProviderError::ParseError(format!(
"Expected 'package' root tag, got {:?}",
root.name
)));
}
let metadata = match root.get_child("metadata") {
Some(metadata) => metadata,
None => {
return Err(ProviderError::ParseError(
"Unable to find metadata tag".to_string(),
));
}
};
let mut result = Vec::new();
if let Some(version_tag) = metadata.get_child("version") {
if let Some(version) = version_tag.get_text() {
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Version(version.into_owned()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
if let Some(description_tag) = metadata.get_child("description") {
if let Some(description) = description_tag.get_text() {
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Description(description.into_owned()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
if let Some(authors_tag) = metadata.get_child("authors") {
if let Some(authors) = authors_tag.get_text() {
let authors = authors.split(',').map(Person::from).collect();
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Author(authors),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
if let Some(project_url_tag) = metadata.get_child("projectUrl") {
if let Some(project_url) = project_url_tag.get_text() {
let parsed_url = match url::Url::parse(&project_url) {
Ok(url) => Some(url),
Err(e) => {
log::warn!(
"nuspec: failed to parse projectUrl {:?}: {}",
project_url,
e
);
None
}
};
let repo_url = if let Some(parsed_url) = &parsed_url {
crate::vcs::guess_repo_from_url(parsed_url, None).await
} else {
None
};
if let Some(repo_url) = repo_url {
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Repository(repo_url),
certainty: Some(Certainty::Confident),
origin: Some(path.into()),
});
}
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Homepage(project_url.into_owned()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
if let Some(license_tag) = metadata.get_child("license") {
if let Some(license) = license_tag.get_text() {
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::License(license.into_owned()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
if let Some(copyright_tag) = metadata.get_child("copyright") {
if let Some(copyright) = copyright_tag.get_text() {
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Copyright(copyright.into_owned()),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
}
if let Some(title_tag) = metadata.get_child("title") {
if let Some(title) = title_tag.get_text() {
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Name(title.into_owned()),
certainty: Some(Certainty::Likely),
origin: Some(path.into()),
});
}
}
if let Some(summary_tag) = metadata.get_child("summary") {
if let Some(summary) = summary_tag.get_text() {
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Summary(summary.into_owned()),
certainty: Some(Certainty::Likely),
origin: Some(path.into()),
});
}
}
if let Some(repository_tag) = metadata.get_child("repository") {
if let Some(repo_url) = repository_tag.attributes.get("url") {
match repo_url.parse() {
Ok(url) => {
let branch = repository_tag.attributes.get("branch");
result.push(UpstreamDatumWithMetadata {
datum: UpstreamDatum::Repository(crate::vcs::unsplit_vcs_url(
&crate::vcs::VcsLocation {
url,
branch: branch.cloned(),
subpath: None,
},
)),
certainty: Some(Certainty::Certain),
origin: Some(path.into()),
});
}
Err(e) => {
log::warn!(
"nuspec: failed to parse repository URL {:?}: {}",
repo_url,
e
);
}
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn write_nuspec(td: &tempfile::TempDir, content: &str) -> std::path::PathBuf {
let path = td.path().join("test.nuspec");
std::fs::write(&path, content).unwrap();
path
}
#[tokio::test]
async fn test_wrong_root_element() {
let td = tempfile::tempdir().unwrap();
let path = write_nuspec(&td, r#"<project><metadata/></project>"#);
let result = guess_from_nuspec(&path, false).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_no_metadata_element() {
let td = tempfile::tempdir().unwrap();
let path = write_nuspec(&td, r#"<package><files/></package>"#);
let result = guess_from_nuspec(&path, false).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_invalid_project_url() {
let td = tempfile::tempdir().unwrap();
let path = write_nuspec(
&td,
r#"<package><metadata>
<projectUrl>not a valid url at all</projectUrl>
</metadata></package>"#,
);
let result = guess_from_nuspec(&path, false).await.unwrap();
assert!(result
.iter()
.any(|d| matches!(&d.datum, UpstreamDatum::Homepage(_))));
}
#[tokio::test]
async fn test_invalid_repository_url() {
let td = tempfile::tempdir().unwrap();
let path = write_nuspec(
&td,
r#"<package><metadata>
<repository url=":::invalid"/>
</metadata></package>"#,
);
let result = guess_from_nuspec(&path, false).await.unwrap();
assert!(result.is_empty());
}
}