normalize_package_index/index/
racket.rs1use super::{IndexError, PackageIndex, PackageMeta, VersionMeta};
13use crate::cache;
14use std::collections::HashMap;
15use std::time::Duration;
16
17const CACHE_TTL: Duration = Duration::from_secs(60 * 60);
19
20pub struct Racket;
22
23impl Racket {
24 const PACKAGES_URL: &'static str = "https://pkgs.racket-lang.org/pkgs-all.json.gz";
26
27 fn parse_package(name: &str, pkg: &serde_json::Value) -> Option<PackageMeta> {
29 let mut extra = HashMap::new();
30
31 if let Some(deps) = pkg["dependencies"].as_array() {
33 let dep_names: Vec<serde_json::Value> = deps
34 .iter()
35 .filter_map(|d| {
36 if let Some(s) = d.as_str() {
38 Some(serde_json::Value::String(s.to_string()))
39 } else if let Some(arr) = d.as_array() {
40 arr.first()
41 .and_then(|f| f.as_str())
42 .map(|s| serde_json::Value::String(s.to_string()))
43 } else {
44 None
45 }
46 })
47 .collect();
48 if !dep_names.is_empty() {
49 extra.insert("depends".to_string(), serde_json::Value::Array(dep_names));
50 }
51 }
52
53 if let Some(tags) = pkg["tags"].as_array() {
55 let tag_list: Vec<serde_json::Value> = tags
56 .iter()
57 .filter_map(|t| t.as_str().map(|s| serde_json::Value::String(s.to_string())))
58 .collect();
59 if !tag_list.is_empty() {
60 extra.insert("keywords".to_string(), serde_json::Value::Array(tag_list));
61 }
62 }
63
64 if let Some(ring) = pkg["ring"].as_u64() {
66 extra.insert("ring".to_string(), serde_json::Value::Number(ring.into()));
67 }
68
69 let source_url = pkg["source"].as_str().map(String::from);
71
72 let checksum = pkg["checksum"].as_str().map(|c| format!("sha1:{}", c));
74
75 let maintainers: Vec<String> = pkg["authors"]
77 .as_array()
78 .map(|authors| {
79 authors
80 .iter()
81 .filter_map(|a| a.as_str().map(String::from))
82 .collect()
83 })
84 .unwrap_or_default();
85
86 let version = pkg["versions"]["default"]["checksum"]
88 .as_str()
89 .map(|c| c[..8].to_string()) .unwrap_or_else(|| "latest".to_string());
91
92 Some(PackageMeta {
93 name: name.to_string(),
94 version,
95 description: pkg["description"].as_str().map(String::from),
96 homepage: Some(format!("https://pkgs.racket-lang.org/package/{}", name)),
97 repository: source_url.clone(),
98 license: pkg["license"].as_str().map(String::from),
99 binaries: Vec::new(),
100 archive_url: source_url,
101 keywords: Vec::new(),
102 maintainers,
103 published: None,
104 downloads: None,
105 checksum,
106 extra,
107 })
108 }
109
110 fn load_all_packages() -> Result<serde_json::Value, IndexError> {
112 let (data, _was_cached) =
113 cache::fetch_with_cache("racket", "pkgs-all", Self::PACKAGES_URL, CACHE_TTL)
114 .map_err(IndexError::Network)?;
115
116 serde_json::from_slice(&data).map_err(|e| IndexError::Parse(e.to_string()))
117 }
118}
119
120impl PackageIndex for Racket {
121 fn ecosystem(&self) -> &'static str {
122 "racket"
123 }
124
125 fn display_name(&self) -> &'static str {
126 "Racket"
127 }
128
129 fn fetch(&self, name: &str) -> Result<PackageMeta, IndexError> {
130 let packages = Self::load_all_packages()?;
131
132 packages
133 .get(name)
134 .and_then(|pkg| Self::parse_package(name, pkg))
135 .ok_or_else(|| IndexError::NotFound(name.to_string()))
136 }
137
138 fn fetch_versions(&self, name: &str) -> Result<Vec<VersionMeta>, IndexError> {
139 let packages = Self::load_all_packages()?;
140
141 let pkg = packages
142 .get(name)
143 .ok_or_else(|| IndexError::NotFound(name.to_string()))?;
144
145 let mut versions = Vec::new();
148
149 if let Some(vers) = pkg["versions"].as_object() {
150 for (ver_name, ver_data) in vers {
151 if let Some(checksum) = ver_data["checksum"].as_str() {
152 versions.push(VersionMeta {
153 version: if ver_name == "default" {
154 checksum[..8].to_string()
155 } else {
156 ver_name.clone()
157 },
158 released: None,
159 yanked: false,
160 });
161 }
162 }
163 }
164
165 if versions.is_empty() {
166 let pkg_meta = Self::parse_package(name, pkg)
167 .ok_or_else(|| IndexError::NotFound(name.to_string()))?;
168 versions.push(VersionMeta {
169 version: pkg_meta.version,
170 released: None,
171 yanked: false,
172 });
173 }
174
175 Ok(versions)
176 }
177
178 fn search(&self, query: &str) -> Result<Vec<PackageMeta>, IndexError> {
179 let packages = Self::load_all_packages()?;
180 let query_lower = query.to_lowercase();
181
182 let results: Vec<PackageMeta> = packages
183 .as_object()
184 .ok_or_else(|| IndexError::Parse("expected object".into()))?
185 .iter()
186 .filter(|(name, pkg)| {
187 name.to_lowercase().contains(&query_lower)
189 || pkg["description"]
191 .as_str()
192 .map(|d| d.to_lowercase().contains(&query_lower))
193 .unwrap_or(false)
194 || pkg["tags"]
196 .as_array()
197 .map(|tags| {
198 tags.iter()
199 .any(|t| t.as_str().map(|s| s.contains(&query_lower)).unwrap_or(false))
200 })
201 .unwrap_or(false)
202 })
203 .take(50)
204 .filter_map(|(name, pkg)| Self::parse_package(name, pkg))
205 .collect();
206
207 Ok(results)
208 }
209
210 fn fetch_all(&self) -> Result<Vec<PackageMeta>, IndexError> {
211 let packages = Self::load_all_packages()?;
212
213 let results: Vec<PackageMeta> = packages
214 .as_object()
215 .ok_or_else(|| IndexError::Parse("expected object".into()))?
216 .iter()
217 .filter_map(|(name, pkg)| Self::parse_package(name, pkg))
218 .collect();
219
220 Ok(results)
221 }
222}