use super::{IndexError, PackageIndex, PackageMeta, VersionMeta};
use std::collections::HashMap;
pub struct Copr;
impl Copr {
const API_BASE: &'static str = "https://copr.fedorainfracloud.org/api_3";
fn parse_project(project: &serde_json::Value) -> Option<PackageMeta> {
let full_name = project["full_name"].as_str()?;
let name = project["name"].as_str().unwrap_or(full_name);
let owner = project["ownername"].as_str().unwrap_or("");
let mut extra = HashMap::new();
extra.insert(
"owner".to_string(),
serde_json::Value::String(owner.to_string()),
);
if let Some(repos) = project["chroot_repos"].as_object() {
let repo_list: Vec<serde_json::Value> = repos
.keys()
.map(|k| serde_json::Value::String(k.clone()))
.collect();
extra.insert("chroots".to_string(), serde_json::Value::Array(repo_list));
}
Some(PackageMeta {
name: name.to_string(),
version: "latest".to_string(), description: project["description"].as_str().map(String::from),
homepage: project["homepage"].as_str().map(String::from).or_else(|| {
Some(format!(
"https://copr.fedorainfracloud.org/coprs/{}/",
full_name
))
}),
repository: None,
license: None,
binaries: Vec::new(),
keywords: Vec::new(),
maintainers: vec![owner.to_string()],
published: None,
downloads: None,
archive_url: None,
checksum: None,
extra,
})
}
fn parse_build(build: &serde_json::Value, project_name: &str) -> Option<PackageMeta> {
let source_package = build["source_package"].as_object()?;
let name = source_package
.get("name")
.and_then(|n| n.as_str())
.unwrap_or(project_name);
let version = source_package
.get("version")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let mut extra = HashMap::new();
if let Some(state) = build["state"].as_str() {
extra.insert(
"build_state".to_string(),
serde_json::Value::String(state.to_string()),
);
}
Some(PackageMeta {
name: name.to_string(),
version: version.to_string(),
description: None,
homepage: Some(format!(
"https://copr.fedorainfracloud.org/coprs/{}/",
project_name
)),
repository: None,
license: None,
binaries: Vec::new(),
keywords: Vec::new(),
maintainers: Vec::new(),
published: None,
downloads: None,
archive_url: None,
checksum: None,
extra,
})
}
}
impl PackageIndex for Copr {
fn ecosystem(&self) -> &'static str {
"copr"
}
fn display_name(&self) -> &'static str {
"Fedora Copr"
}
fn fetch(&self, name: &str) -> Result<PackageMeta, IndexError> {
let (owner, project) = if name.contains('/') {
let parts: Vec<&str> = name.splitn(2, '/').collect();
(parts[0], parts[1])
} else {
let results = self.search(name)?;
return results
.into_iter()
.find(|p| p.name.eq_ignore_ascii_case(name))
.ok_or_else(|| IndexError::NotFound(name.to_string()));
};
let url = format!(
"{}/project?ownername={}&projectname={}",
Self::API_BASE,
owner,
project
);
let response: serde_json::Value = ureq::get(&url).call()?.into_json()?;
Self::parse_project(&response).ok_or_else(|| IndexError::NotFound(name.to_string()))
}
fn fetch_versions(&self, name: &str) -> Result<Vec<VersionMeta>, IndexError> {
let (owner, project) = if name.contains('/') {
let parts: Vec<&str> = name.splitn(2, '/').collect();
(parts[0], parts[1])
} else {
return Ok(vec![VersionMeta {
version: "latest".to_string(),
released: None,
yanked: false,
}]);
};
let url = format!(
"{}/build/list?ownername={}&projectname={}&limit=20",
Self::API_BASE,
owner,
project
);
match ureq::get(&url).call() {
Ok(resp) => {
let response: serde_json::Value = resp.into_json()?;
let mut versions = Vec::new();
if let Some(items) = response["items"].as_array() {
for build in items {
if let Some(pkg) = Self::parse_build(build, name)
&& !versions
.iter()
.any(|v: &VersionMeta| v.version == pkg.version)
{
versions.push(VersionMeta {
version: pkg.version,
released: None,
yanked: false,
});
}
}
}
if versions.is_empty() {
versions.push(VersionMeta {
version: "latest".to_string(),
released: None,
yanked: false,
});
}
Ok(versions)
}
Err(_) => Ok(vec![VersionMeta {
version: "latest".to_string(),
released: None,
yanked: false,
}]),
}
}
fn search(&self, query: &str) -> Result<Vec<PackageMeta>, IndexError> {
let url = format!(
"{}/project/search?query={}&limit=50",
Self::API_BASE,
urlencoding::encode(query)
);
let response: serde_json::Value = ureq::get(&url).call()?.into_json()?;
let packages: Vec<PackageMeta> = response["items"]
.as_array()
.map(|items| items.iter().filter_map(Self::parse_project).collect())
.unwrap_or_default();
Ok(packages)
}
}