1use serde::{Deserialize, Serialize};
15use std::collections::BTreeMap;
16
17#[derive(Serialize, Deserialize, Default)]
18pub struct IndexFile {
19 #[serde(flatten)]
24 versions: BTreeMap<semver::Version, PackageEntry>,
25}
26
27#[derive(Serialize, Deserialize, Clone)]
39pub struct PackageEntry {
40 #[serde(alias = "package_name")]
44 name: String,
45 version: semver::Version,
49 source_cid: String,
53 abi_cid: Option<String>,
58 dependencies: Vec<PackageDependencyIdentifier>,
61 yanked: bool,
64}
65
66#[derive(Serialize, Deserialize, Clone)]
67pub struct PackageDependencyIdentifier {
68 package_name: String,
72 version: String,
76}
77
78impl PackageEntry {
79 pub fn new(
80 name: String,
81 version: semver::Version,
82 source_cid: String,
83 abi_cid: Option<String>,
84 dependencies: Vec<PackageDependencyIdentifier>,
85 yanked: bool,
86 ) -> Self {
87 Self {
88 name,
89 version,
90 source_cid,
91 abi_cid,
92 dependencies,
93 yanked,
94 }
95 }
96
97 pub fn name(&self) -> &str {
99 &self.name
100 }
101
102 pub fn version(&self) -> &semver::Version {
104 &self.version
105 }
106
107 pub fn source_cid(&self) -> &str {
109 &self.source_cid
110 }
111
112 pub fn abi_cid(&self) -> Option<&str> {
114 self.abi_cid.as_deref()
115 }
116
117 pub fn dependencies(&self) -> impl Iterator<Item = &PackageDependencyIdentifier> {
119 self.dependencies.iter()
120 }
121
122 pub fn yanked(&self) -> bool {
124 self.yanked
125 }
126}
127
128impl PackageDependencyIdentifier {
129 pub fn new(package_name: String, version: String) -> Self {
130 Self {
131 package_name,
132 version,
133 }
134 }
135}
136impl IndexFile {
137 pub fn get(&self, version: &semver::Version) -> Option<&PackageEntry> {
140 self.versions.get(version)
141 }
142
143 pub fn insert(&mut self, package: PackageEntry) {
147 let pkg_version = package.version().clone();
148 self.versions.insert(pkg_version, package);
149 }
150
151 pub fn versions(&self) -> impl Iterator<Item = &semver::Version> {
153 self.versions.keys()
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_serialize_deserialize_empty_index() {
163 let index = IndexFile {
164 versions: BTreeMap::new(),
165 };
166
167 let serialized = serde_json::to_string(&index).unwrap();
168 assert_eq!(serialized, "{}");
169 let deserialized: IndexFile = serde_json::from_str(&serialized).unwrap();
170 assert_eq!(deserialized.versions.len(), 0);
171 }
172
173 #[test]
174 fn test_json_format() {
175 let json = r#"{
177 "0.0.1":{
178 "package_name":"tester",
179 "version":"0.0.1",
180 "source_cid":"QmOlderHash",
181 "abi_cid":"QmOlderAbiHash",
182 "dependencies":[],
183 "yanked": false
184 },
185 "0.0.2":{
186 "package_name":"tester",
187 "version":"0.0.2",
188 "source_cid":"QmExampleHash",
189 "abi_cid":"QmExampleAbiHash",
190 "dependencies":[],
191 "yanked": false
192 }
193 }"#;
194
195 let deserialized: IndexFile = serde_json::from_str(json).unwrap();
196
197 assert_eq!(deserialized.versions.len(), 2);
198 assert!(deserialized
199 .versions
200 .contains_key(&semver::Version::new(0, 0, 1)));
201 assert!(deserialized
202 .versions
203 .contains_key(&semver::Version::new(0, 0, 2)));
204
205 let v011 = &deserialized.versions[&semver::Version::new(0, 0, 1)];
206 assert_eq!(v011.source_cid, "QmOlderHash");
207 assert_eq!(v011.abi_cid, Some("QmOlderAbiHash".to_string()));
208 assert_eq!(v011.dependencies.len(), 0);
209
210 let v012 = &deserialized.versions[&semver::Version::new(0, 0, 2)];
211 assert_eq!(v012.source_cid, "QmExampleHash");
212 assert_eq!(v012.abi_cid, Some("QmExampleAbiHash".to_string()));
213 assert_eq!(v012.dependencies.len(), 0);
214 }
215
216 #[test]
217 fn test_add_new_package_entry_and_parse_back() {
218 let json = r#"{
219 "1.0.0": {
220 "name": "existing-package",
221 "version": "1.0.0",
222 "source_cid": "QmExistingHash",
223 "abi_cid": "QmExistingAbiHash",
224 "dependencies": [
225 {
226 "package_name": "dep1",
227 "version": "^0.5.0"
228 }
229 ],
230 "yanked": false
231 }
232 }"#;
233
234 let mut index_file: IndexFile = serde_json::from_str(json).unwrap();
235
236 assert_eq!(index_file.versions.len(), 1);
237 assert!(index_file
238 .versions
239 .contains_key(&semver::Version::new(1, 0, 0)));
240
241 let dependencies = vec![
242 PackageDependencyIdentifier::new("new-dep1".to_string(), "^1.0.0".to_string()),
243 PackageDependencyIdentifier::new("new-dep2".to_string(), "=0.9.0".to_string()),
244 ];
245
246 let yanked = false;
247
248 let new_package = PackageEntry::new(
249 "new-package".to_string(),
250 semver::Version::new(2, 1, 0),
251 "QmNewPackageHash".to_string(),
252 Some("QmNewPackageAbiHash".to_string()),
253 dependencies,
254 yanked,
255 );
256
257 index_file.insert(new_package);
258
259 assert_eq!(index_file.versions.len(), 2);
260 assert!(index_file
261 .versions
262 .contains_key(&semver::Version::new(1, 0, 0)));
263 assert!(index_file
264 .versions
265 .contains_key(&semver::Version::new(2, 1, 0)));
266
267 let updated_json = serde_json::to_string_pretty(&index_file).unwrap();
268 let reparsed_index: IndexFile = serde_json::from_str(&updated_json).unwrap();
269
270 assert_eq!(reparsed_index.versions.len(), 2);
271 assert!(reparsed_index
272 .versions
273 .contains_key(&semver::Version::new(1, 0, 0)));
274 assert!(reparsed_index
275 .versions
276 .contains_key(&semver::Version::new(2, 1, 0)));
277
278 let new_pkg = reparsed_index.get(&semver::Version::new(2, 1, 0)).unwrap();
279 assert_eq!(new_pkg.name(), "new-package");
280 assert_eq!(new_pkg.version(), &semver::Version::new(2, 1, 0));
281 assert_eq!(new_pkg.source_cid(), "QmNewPackageHash");
282 assert_eq!(new_pkg.abi_cid(), Some("QmNewPackageAbiHash"));
283
284 let deps: Vec<_> = new_pkg.dependencies().collect();
285 assert_eq!(deps.len(), 2);
286 assert_eq!(deps[0].package_name, "new-dep1");
287 assert_eq!(deps[0].version, "^1.0.0");
288 assert_eq!(deps[1].package_name, "new-dep2");
289 assert_eq!(deps[1].version, "=0.9.0");
290
291 let orig_pkg = reparsed_index.get(&semver::Version::new(1, 0, 0)).unwrap();
292 assert_eq!(orig_pkg.name(), "existing-package");
293 assert_eq!(orig_pkg.source_cid(), "QmExistingHash");
294 }
295
296 #[test]
297 fn test_json_with_dependencies() {
298 let json = r#"{
300 "1.0.0": {
301 "package_name": "main-package",
302 "version": "1.0.0",
303 "source_cid": "QmMainHash",
304 "abi_cid": null,
305 "dependencies": [
306 {
307 "package_name": "dep-package",
308 "version": "^0.5.0"
309 },
310 {
311 "package_name": "another-dep",
312 "version": "=0.9.1"
313 },
314 {
315 "package_name": "third-dep",
316 "version": "0.2.0"
317 }
318 ],
319 "yanked": false
320 }
321 }"#;
322
323 let deserialized: IndexFile = serde_json::from_str(json).unwrap();
324
325 assert_eq!(deserialized.versions.len(), 1);
327 assert!(deserialized
328 .versions
329 .contains_key(&semver::Version::new(1, 0, 0)));
330
331 let main_pkg = &deserialized.versions[&semver::Version::new(1, 0, 0)];
332 assert_eq!(main_pkg.name, "main-package");
333 assert_eq!(main_pkg.source_cid, "QmMainHash");
334 assert_eq!(main_pkg.abi_cid, None);
335 assert!(!main_pkg.yanked);
336
337 assert_eq!(main_pkg.dependencies.len(), 3);
339
340 let dep1 = &main_pkg.dependencies[0];
342 assert_eq!(dep1.package_name, "dep-package");
343 assert_eq!(dep1.version, "^0.5.0");
344
345 let dep2 = &main_pkg.dependencies[1];
347 assert_eq!(dep2.package_name, "another-dep");
348 assert_eq!(dep2.version, "=0.9.1");
349
350 let dep3 = &main_pkg.dependencies[2];
352 assert_eq!(dep3.package_name, "third-dep");
353 assert_eq!(dep3.version, "0.2.0");
354
355 let serialized = serde_json::to_string_pretty(&deserialized).unwrap();
357 println!("Re-serialized JSON: {}", serialized);
358
359 let re_deserialized: IndexFile = serde_json::from_str(&serialized).unwrap();
361 assert_eq!(re_deserialized.versions.len(), 1);
362
363 let main_pkg2 = &re_deserialized.versions[&semver::Version::new(1, 0, 0)];
365 assert_eq!(main_pkg2.dependencies.len(), 3);
366 }
367
368 #[test]
369 fn test_json_with_missing_optional_fields() {
370 let json = r#"{
372 "0.5.0": {
373 "package_name": "minimal-package",
374 "version": "0.5.0",
375 "source_cid": "QmMinimalHash",
376 "dependencies": [],
377 "yanked": false
378 }
379 }"#;
380
381 let deserialized: IndexFile = serde_json::from_str(json).unwrap();
382
383 assert_eq!(deserialized.versions.len(), 1);
384 let pkg = &deserialized.versions[&semver::Version::new(0, 5, 0)];
385 assert_eq!(pkg.name, "minimal-package");
386 assert_eq!(pkg.source_cid, "QmMinimalHash");
387 assert_eq!(pkg.abi_cid, None);
388 assert_eq!(pkg.dependencies.len(), 0);
389 }
390}