normalize_package_index/index/
bioconductor.rs1use super::{IndexError, PackageIndex, PackageMeta, VersionMeta};
13use std::collections::HashMap;
14
15pub struct Bioconductor;
17
18impl Bioconductor {
19 const API_BASE: &'static str = "https://bioconductor.r-universe.dev/api";
21
22 fn parse_package(pkg: &serde_json::Value) -> Option<PackageMeta> {
24 let name = pkg["Package"].as_str()?;
25 let version = pkg["Version"].as_str().unwrap_or("unknown");
26
27 let mut extra = HashMap::new();
28
29 let mut deps = Vec::new();
31 for dep_field in ["Imports", "Depends", "LinkingTo"] {
32 if let Some(dep_str) = pkg[dep_field].as_str() {
33 for dep in dep_str.split(',') {
34 let dep_name = dep
35 .trim()
36 .split(|c| c == ' ' || c == '(' || c == '\n')
37 .next()
38 .unwrap_or("")
39 .trim();
40 if !dep_name.is_empty() && dep_name != "R" {
41 deps.push(serde_json::Value::String(dep_name.to_string()));
42 }
43 }
44 }
45 }
46 if !deps.is_empty() {
47 extra.insert("depends".to_string(), serde_json::Value::Array(deps));
48 }
49
50 if let Some(size) = pkg["_filesize"].as_u64() {
52 extra.insert("size".to_string(), serde_json::Value::Number(size.into()));
53 }
54
55 let archive_url = pkg["_file"]
57 .as_str()
58 .map(|file| format!("https://bioconductor.r-universe.dev/src/contrib/{}", file));
59
60 let checksum = pkg["_sha256"].as_str().map(|s| format!("sha256:{}", s));
62
63 let maintainers: Vec<String> = pkg["Maintainer"]
65 .as_str()
66 .map(|m| vec![m.to_string()])
67 .unwrap_or_default();
68
69 Some(PackageMeta {
70 name: name.to_string(),
71 version: version.to_string(),
72 description: pkg["Title"].as_str().map(String::from),
73 homepage: pkg["URL"].as_str().map(String::from),
74 repository: pkg["RemoteUrl"].as_str().map(String::from),
75 license: pkg["License"].as_str().map(String::from),
76 binaries: Vec::new(),
77 archive_url,
78 keywords: Vec::new(),
79 maintainers,
80 published: None,
81 downloads: None,
82 checksum,
83 extra,
84 })
85 }
86}
87
88impl PackageIndex for Bioconductor {
89 fn ecosystem(&self) -> &'static str {
90 "bioconductor"
91 }
92
93 fn display_name(&self) -> &'static str {
94 "Bioconductor"
95 }
96
97 fn fetch(&self, name: &str) -> Result<PackageMeta, IndexError> {
98 let url = format!("{}/packages/{}", Self::API_BASE, name);
99 let response: serde_json::Value = ureq::get(&url).call()?.into_json()?;
100
101 Self::parse_package(&response).ok_or_else(|| IndexError::NotFound(name.to_string()))
102 }
103
104 fn fetch_versions(&self, name: &str) -> Result<Vec<VersionMeta>, IndexError> {
105 let url = format!("{}/packages/{}/versions", Self::API_BASE, name);
107
108 match ureq::get(&url).call() {
109 Ok(resp) => {
110 let versions: Vec<serde_json::Value> = resp.into_json()?;
111 Ok(versions
112 .iter()
113 .filter_map(|v| {
114 Some(VersionMeta {
115 version: v["Version"].as_str()?.to_string(),
116 released: v["_published"].as_str().map(String::from),
117 yanked: false,
118 })
119 })
120 .collect())
121 }
122 Err(_) => {
123 let pkg = self.fetch(name)?;
125 Ok(vec![VersionMeta {
126 version: pkg.version,
127 released: None,
128 yanked: false,
129 }])
130 }
131 }
132 }
133
134 fn search(&self, query: &str) -> Result<Vec<PackageMeta>, IndexError> {
135 let url = format!(
137 "{}/packages?q={}&limit=50",
138 Self::API_BASE,
139 urlencoding::encode(query)
140 );
141 let response: Vec<serde_json::Value> = ureq::get(&url).call()?.into_json()?;
142
143 Ok(response.iter().filter_map(Self::parse_package).collect())
144 }
145
146 fn fetch_all(&self) -> Result<Vec<PackageMeta>, IndexError> {
147 let url = format!("{}/packages", Self::API_BASE);
148 let response: Vec<serde_json::Value> = ureq::get(&url).call()?.into_json()?;
149
150 Ok(response.iter().filter_map(Self::parse_package).collect())
151 }
152}