Skip to main content

valheim_mod_manager/
package.rs

1use crate::intern::{
2  InternKey, StringInterner, intern_option, intern_vec, resolve_option, resolve_vec,
3};
4use lasso::Key;
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeSet;
7use std::collections::HashMap;
8use time::OffsetDateTime;
9
10/// Struct-of-Arrays representation of the package manifest for efficient storage.
11///
12/// This structure organizes package data in a columnar format to minimize duplication
13/// and improve serialization efficiency. All packages are stored with parallel vectors,
14/// and version data is stored separately with indices linking back to packages.
15#[derive(Serialize, Deserialize, Debug, Clone)]
16pub struct PackageManifest {
17  pub names: Vec<Option<String>>,
18  pub full_names: Vec<Option<String>>,
19  pub owners: Vec<Option<String>>,
20  pub package_urls: Vec<Option<String>>,
21  pub dates_created: Vec<OffsetDateTime>,
22  pub dates_updated: Vec<OffsetDateTime>,
23  pub uuid4s: Vec<Option<String>>,
24  pub rating_scores: Vec<Option<u32>>,
25  pub is_pinned: Vec<Option<bool>>,
26  pub is_deprecated: Vec<Option<bool>>,
27  pub has_nsfw_content: Vec<Option<bool>>,
28  pub categories: Vec<Vec<String>>,
29  pub version_ranges: Vec<(usize, usize)>,
30  pub versions: VersionManifest,
31}
32
33/// Struct-of-Arrays representation of version data.
34///
35/// Separates frequently accessed "hot" data from rarely accessed "cold" metadata
36/// for better cache performance and memory efficiency.
37#[derive(Serialize, Deserialize, Debug, Clone)]
38pub struct VersionManifest {
39  pub package_indices: Vec<usize>,
40  pub version_numbers: Vec<Option<String>>,
41  pub download_urls: Vec<Option<String>>,
42  pub dependencies: Vec<Vec<String>>,
43  pub dates_created: Vec<OffsetDateTime>,
44  pub descriptions: Vec<Option<String>>,
45  pub icons: Vec<Option<String>>,
46  pub downloads: Vec<Option<u32>>,
47  pub website_urls: Vec<Option<String>>,
48  pub is_active: Vec<Option<bool>>,
49  pub uuid4s: Vec<Option<String>>,
50  pub file_sizes: Vec<Option<u64>>,
51}
52
53/// Struct-of-Arrays representation of the package manifest with interned strings.
54///
55/// This structure builds upon the PackageManifest format by adding string interning
56/// to further reduce memory usage and improve serialization efficiency. Repeated strings
57/// like author names, categories, and URLs are stored once in the interner and referenced
58/// by integer keys throughout the structure.
59///
60/// String interning is particularly beneficial for the Valheim mod ecosystem where the same
61/// authors maintain multiple packages and common patterns appear frequently in metadata.
62#[derive(Debug, Clone)]
63pub struct InternedPackageManifest {
64  pub interner: StringInterner,
65  pub names: Vec<Option<InternKey>>,
66  pub full_names: Vec<Option<InternKey>>,
67  pub owners: Vec<Option<InternKey>>,
68  pub package_urls: Vec<Option<InternKey>>,
69  pub dates_created: Vec<OffsetDateTime>,
70  pub dates_updated: Vec<OffsetDateTime>,
71  pub uuid4s: Vec<Option<InternKey>>,
72  pub rating_scores: Vec<Option<u32>>,
73  pub is_pinned: Vec<Option<bool>>,
74  pub is_deprecated: Vec<Option<bool>>,
75  pub has_nsfw_content: Vec<Option<bool>>,
76  pub categories: Vec<Vec<InternKey>>,
77  pub version_ranges: Vec<(usize, usize)>,
78  pub versions: InternedVersionManifest,
79}
80
81/// Struct-of-Arrays representation of version data with interned strings.
82///
83/// Separates frequently accessed "hot" data from rarely accessed "cold" metadata
84/// for better cache performance and memory efficiency. String values are stored as
85/// interned keys that reference the parent manifest's string interner.
86#[derive(Debug, Clone)]
87pub struct InternedVersionManifest {
88  pub package_indices: Vec<usize>,
89  pub version_numbers: Vec<Option<InternKey>>,
90  pub download_urls: Vec<Option<InternKey>>,
91  pub dependencies: Vec<Vec<InternKey>>,
92  pub dates_created: Vec<OffsetDateTime>,
93  pub descriptions: Vec<Option<InternKey>>,
94  pub icons: Vec<Option<InternKey>>,
95  pub downloads: Vec<Option<u32>>,
96  pub website_urls: Vec<Option<InternKey>>,
97  pub is_active: Vec<Option<bool>>,
98  pub uuid4s: Vec<Option<InternKey>>,
99  pub file_sizes: Vec<Option<u64>>,
100}
101
102/// Serializable representation of an interned package manifest.
103///
104/// This structure converts the runtime InternedPackageManifest into a format that can be
105/// efficiently serialized and deserialized. The string interner is converted to a simple
106/// vector of strings (the string table), and all interned keys become u32 indices into
107/// this table.
108///
109/// This separation allows for optimal serialization performance while maintaining the
110/// memory efficiency benefits of string interning at runtime.
111#[derive(Serialize, Deserialize, Debug, Clone)]
112pub struct SerializableInternedManifest {
113  pub string_table: Vec<String>,
114  pub names: Vec<Option<u32>>,
115  pub full_names: Vec<Option<u32>>,
116  pub owners: Vec<Option<u32>>,
117  pub package_urls: Vec<Option<u32>>,
118  pub dates_created: Vec<OffsetDateTime>,
119  pub dates_updated: Vec<OffsetDateTime>,
120  pub uuid4s: Vec<Option<u32>>,
121  pub rating_scores: Vec<Option<u32>>,
122  pub is_pinned: Vec<Option<bool>>,
123  pub is_deprecated: Vec<Option<bool>>,
124  pub has_nsfw_content: Vec<Option<bool>>,
125  pub categories: Vec<Vec<u32>>,
126  pub version_ranges: Vec<(usize, usize)>,
127  pub versions: SerializableInternedVersionManifest,
128}
129
130/// Serializable representation of interned version data.
131///
132/// Contains version information with string values represented as u32 indices into
133/// the parent manifest's string table. This allows efficient serialization while
134/// maintaining the benefits of string interning.
135#[derive(Serialize, Deserialize, Debug, Clone)]
136pub struct SerializableInternedVersionManifest {
137  pub package_indices: Vec<usize>,
138  pub version_numbers: Vec<Option<u32>>,
139  pub download_urls: Vec<Option<u32>>,
140  pub dependencies: Vec<Vec<u32>>,
141  pub dates_created: Vec<OffsetDateTime>,
142  pub descriptions: Vec<Option<u32>>,
143  pub icons: Vec<Option<u32>>,
144  pub downloads: Vec<Option<u32>>,
145  pub website_urls: Vec<Option<u32>>,
146  pub is_active: Vec<Option<bool>>,
147  pub uuid4s: Vec<Option<u32>>,
148  pub file_sizes: Vec<Option<u64>>,
149}
150
151#[allow(dead_code)]
152impl PackageManifest {
153  /// Returns the number of packages in the manifest.
154  ///
155  /// # Returns
156  ///
157  /// The total count of packages
158  pub fn len(&self) -> usize {
159    self.names.len()
160  }
161
162  /// Returns true if the manifest contains no packages.
163  ///
164  /// # Returns
165  ///
166  /// `true` if empty, `false` otherwise
167  pub fn is_empty(&self) -> bool {
168    self.names.is_empty()
169  }
170
171  /// Validates the internal consistency of the manifest structure.
172  ///
173  /// Checks that all parallel vectors have matching lengths and that version
174  /// ranges are valid.
175  ///
176  /// # Returns
177  ///
178  /// Ok if the manifest is valid, otherwise an error describing the problem.
179  pub fn validate(&self) -> Result<(), String> {
180    let pkg_count = self.names.len();
181
182    if self.full_names.len() != pkg_count {
183      return Err(format!(
184        "full_names length {} != {}",
185        self.full_names.len(),
186        pkg_count
187      ));
188    }
189
190    if self.owners.len() != pkg_count {
191      return Err(format!(
192        "owners length {} != {}",
193        self.owners.len(),
194        pkg_count
195      ));
196    }
197
198    if self.package_urls.len() != pkg_count {
199      return Err(format!(
200        "package_urls length {} != {}",
201        self.package_urls.len(),
202        pkg_count
203      ));
204    }
205
206    if self.dates_created.len() != pkg_count {
207      return Err(format!(
208        "dates_created length {} != {}",
209        self.dates_created.len(),
210        pkg_count
211      ));
212    }
213
214    if self.dates_updated.len() != pkg_count {
215      return Err(format!(
216        "dates_updated length {} != {}",
217        self.dates_updated.len(),
218        pkg_count
219      ));
220    }
221
222    if self.uuid4s.len() != pkg_count {
223      return Err(format!(
224        "uuid4s length {} != {}",
225        self.uuid4s.len(),
226        pkg_count
227      ));
228    }
229
230    if self.rating_scores.len() != pkg_count {
231      return Err(format!(
232        "rating_scores length {} != {}",
233        self.rating_scores.len(),
234        pkg_count
235      ));
236    }
237
238    if self.is_pinned.len() != pkg_count {
239      return Err(format!(
240        "is_pinned length {} != {}",
241        self.is_pinned.len(),
242        pkg_count
243      ));
244    }
245
246    if self.is_deprecated.len() != pkg_count {
247      return Err(format!(
248        "is_deprecated length {} != {}",
249        self.is_deprecated.len(),
250        pkg_count
251      ));
252    }
253
254    if self.has_nsfw_content.len() != pkg_count {
255      return Err(format!(
256        "has_nsfw_content length {} != {}",
257        self.has_nsfw_content.len(),
258        pkg_count
259      ));
260    }
261
262    if self.categories.len() != pkg_count {
263      return Err(format!(
264        "categories length {} != {}",
265        self.categories.len(),
266        pkg_count
267      ));
268    }
269
270    if self.version_ranges.len() != pkg_count {
271      return Err(format!(
272        "version_ranges length {} != {}",
273        self.version_ranges.len(),
274        pkg_count
275      ));
276    }
277
278    let version_count = self.versions.version_numbers.len();
279
280    if self.versions.package_indices.len() != version_count {
281      return Err(format!(
282        "versions.package_indices length {} != {}",
283        self.versions.package_indices.len(),
284        version_count
285      ));
286    }
287
288    if self.versions.download_urls.len() != version_count {
289      return Err(format!(
290        "versions.download_urls length {} != {}",
291        self.versions.download_urls.len(),
292        version_count
293      ));
294    }
295
296    if self.versions.dependencies.len() != version_count {
297      return Err(format!(
298        "versions.dependencies length {} != {}",
299        self.versions.dependencies.len(),
300        version_count
301      ));
302    }
303
304    if self.versions.dates_created.len() != version_count {
305      return Err(format!(
306        "versions.dates_created length {} != {}",
307        self.versions.dates_created.len(),
308        version_count
309      ));
310    }
311
312    if self.versions.descriptions.len() != version_count {
313      return Err(format!(
314        "versions.descriptions length {} != {}",
315        self.versions.descriptions.len(),
316        version_count
317      ));
318    }
319
320    if self.versions.icons.len() != version_count {
321      return Err(format!(
322        "versions.icons length {} != {}",
323        self.versions.icons.len(),
324        version_count
325      ));
326    }
327
328    if self.versions.downloads.len() != version_count {
329      return Err(format!(
330        "versions.downloads length {} != {}",
331        self.versions.downloads.len(),
332        version_count
333      ));
334    }
335
336    if self.versions.website_urls.len() != version_count {
337      return Err(format!(
338        "versions.website_urls length {} != {}",
339        self.versions.website_urls.len(),
340        version_count
341      ));
342    }
343
344    if self.versions.is_active.len() != version_count {
345      return Err(format!(
346        "versions.is_active length {} != {}",
347        self.versions.is_active.len(),
348        version_count
349      ));
350    }
351
352    if self.versions.uuid4s.len() != version_count {
353      return Err(format!(
354        "versions.uuid4s length {} != {}",
355        self.versions.uuid4s.len(),
356        version_count
357      ));
358    }
359
360    if self.versions.file_sizes.len() != version_count {
361      return Err(format!(
362        "versions.file_sizes length {} != {}",
363        self.versions.file_sizes.len(),
364        version_count
365      ));
366    }
367
368    for (idx, (start, end)) in self.version_ranges.iter().enumerate() {
369      if start > end {
370        return Err(format!(
371          "Invalid version range at package {}: {} > {}",
372          idx, start, end
373        ));
374      }
375
376      if *end > version_count {
377        return Err(format!(
378          "Version range at package {} ends at {} but only {} versions exist",
379          idx, end, version_count
380        ));
381      }
382    }
383
384    Ok(())
385  }
386
387  /// Retrieves a package by its full name.
388  ///
389  /// # Parameters
390  ///
391  /// * `full_name` - The full name of the package to find
392  ///
393  /// # Returns
394  ///
395  /// The package if found, otherwise None.
396  #[allow(dead_code)]
397  pub fn get_package_by_full_name(&self, full_name: &str) -> Option<Package> {
398    let idx = self.find_index_by_full_name(full_name)?;
399    Some(self.get_package_at(idx))
400  }
401
402  /// Finds the index of a package by its full name.
403  ///
404  /// # Parameters
405  ///
406  /// * `full_name` - The full name to search for
407  ///
408  /// # Returns
409  ///
410  /// The index of the package if found, otherwise None.
411  #[allow(dead_code)]
412  pub fn find_index_by_full_name(&self, full_name: &str) -> Option<usize> {
413    self
414      .full_names
415      .iter()
416      .position(|name| name.as_ref().map(|n| n.as_str()) == Some(full_name))
417  }
418
419  /// Reconstructs a Package struct from the manifest data at the given index.
420  ///
421  /// This method gathers all package data and its versions from the SoA structure
422  /// and reconstructs a complete Package object.
423  ///
424  /// # Parameters
425  ///
426  /// * `idx` - The index of the package in the manifest
427  ///
428  /// # Returns
429  ///
430  /// A fully materialized Package struct.
431  #[allow(dead_code)]
432  pub fn get_package_at(&self, idx: usize) -> Package {
433    let (version_start, version_end) = self.version_ranges[idx];
434
435    let mut versions = Vec::with_capacity(version_end - version_start);
436
437    for ver_idx in version_start..version_end {
438      versions.push(Version {
439        name: self.names[idx].clone(),
440        full_name: self.full_names[idx].clone(),
441        description: self.versions.descriptions[ver_idx].clone(),
442        icon: self.versions.icons[ver_idx].clone(),
443        version_number: self.versions.version_numbers[ver_idx].clone(),
444        dependencies: self.versions.dependencies[ver_idx].clone(),
445        download_url: self.versions.download_urls[ver_idx].clone(),
446        downloads: self.versions.downloads[ver_idx],
447        date_created: self.versions.dates_created[ver_idx],
448        website_url: self.versions.website_urls[ver_idx].clone(),
449        is_active: self.versions.is_active[ver_idx],
450        uuid4: self.versions.uuid4s[ver_idx].clone(),
451        file_size: self.versions.file_sizes[ver_idx],
452      });
453    }
454
455    Package {
456      name: self.names[idx].clone(),
457      full_name: self.full_names[idx].clone(),
458      owner: self.owners[idx].clone(),
459      package_url: self.package_urls[idx].clone(),
460      date_created: self.dates_created[idx],
461      date_updated: self.dates_updated[idx],
462      uuid4: self.uuid4s[idx].clone(),
463      rating_score: self.rating_scores[idx],
464      is_pinned: self.is_pinned[idx],
465      is_deprecated: self.is_deprecated[idx],
466      has_nsfw_content: self.has_nsfw_content[idx],
467      categories: self.categories[idx].clone(),
468      versions,
469    }
470  }
471
472  /// Returns the index of the latest version for a package.
473  ///
474  /// The latest version is determined by comparing the date_created field of all versions.
475  ///
476  /// # Parameters
477  ///
478  /// * `idx` - The index of the package in the manifest
479  ///
480  /// # Returns
481  ///
482  /// The index of the latest version, or None if the package has no versions.
483  pub fn get_latest_version_at(&self, idx: usize) -> Option<usize> {
484    let (ver_start, ver_end) = self.version_ranges[idx];
485
486    if ver_start >= ver_end {
487      return None;
488    }
489
490    (ver_start..ver_end).max_by_key(|&ver_idx| self.versions.dates_created[ver_idx])
491  }
492
493  /// Builds an index mapping full package names to their indices.
494  ///
495  /// This is useful for fast lookups of packages by name.
496  ///
497  /// # Returns
498  ///
499  /// A HashMap mapping full names to package indices.
500  pub fn build_name_index(&self) -> HashMap<String, usize> {
501    self
502      .full_names
503      .iter()
504      .enumerate()
505      .filter_map(|(idx, name)| Some((name.as_ref()?.clone(), idx)))
506      .collect()
507  }
508}
509
510/// Implementation block for VersionManifest.
511///
512/// Currently empty but reserved for future version-specific methods.
513impl VersionManifest {}
514
515impl InternedPackageManifest {
516  /// Returns the number of packages in the manifest.
517  pub fn len(&self) -> usize {
518    self.names.len()
519  }
520
521  /// Returns true if the manifest contains no packages.
522  #[allow(dead_code)]
523  pub fn is_empty(&self) -> bool {
524    self.names.is_empty()
525  }
526
527  /// Resolves the package name at the given index from the string interner.
528  ///
529  /// # Returns
530  ///
531  /// The resolved string if a name exists at the index, otherwise None.
532  pub fn resolve_name_at(&self, idx: usize) -> Option<String> {
533    resolve_option(&self.interner, self.names[idx])
534  }
535
536  /// Resolves the full package name at the given index from the string interner.
537  ///
538  /// # Returns
539  ///
540  /// The resolved string if a full name exists at the index, otherwise None.
541  pub fn resolve_full_name_at(&self, idx: usize) -> Option<String> {
542    resolve_option(&self.interner, self.full_names[idx])
543  }
544
545  /// Resolves the package owner at the given index from the string interner.
546  ///
547  /// # Returns
548  ///
549  /// The resolved string if an owner exists at the index, otherwise None.
550  pub fn resolve_owner_at(&self, idx: usize) -> Option<String> {
551    resolve_option(&self.interner, self.owners[idx])
552  }
553
554  /// Retrieves a package by its full name.
555  ///
556  /// # Parameters
557  ///
558  /// * `full_name` - The full name of the package to find
559  ///
560  /// # Returns
561  ///
562  /// The package if found, otherwise None.
563  #[allow(dead_code)]
564  pub fn get_package_by_full_name(&self, full_name: &str) -> Option<Package> {
565    let idx = self.find_index_by_full_name(full_name)?;
566    Some(self.get_package_at(idx))
567  }
568
569  /// Finds the index of a package by its full name.
570  ///
571  /// # Parameters
572  ///
573  /// * `full_name` - The full name to search for
574  ///
575  /// # Returns
576  ///
577  /// The index of the package if found, otherwise None.
578  #[allow(dead_code)]
579  pub fn find_index_by_full_name(&self, full_name: &str) -> Option<usize> {
580    self.full_names.iter().position(|key| {
581      key
582        .map(|k| self.interner.resolve(&k) == full_name)
583        .unwrap_or(false)
584    })
585  }
586
587  /// Reconstructs a Package struct from the manifest data at the given index.
588  ///
589  /// This method resolves all interned strings and constructs a complete Package
590  /// with all its versions.
591  ///
592  /// # Parameters
593  ///
594  /// * `idx` - The index of the package in the manifest
595  ///
596  /// # Returns
597  ///
598  /// A fully materialized Package struct.
599  #[allow(dead_code)]
600  pub fn get_package_at(&self, idx: usize) -> Package {
601    let (version_start, version_end) = self.version_ranges[idx];
602    let mut versions = Vec::with_capacity(version_end - version_start);
603
604    for ver_idx in version_start..version_end {
605      versions.push(Version {
606        name: resolve_option(&self.interner, self.names[idx]),
607        full_name: resolve_option(&self.interner, self.full_names[idx]),
608        description: resolve_option(&self.interner, self.versions.descriptions[ver_idx]),
609        icon: resolve_option(&self.interner, self.versions.icons[ver_idx]),
610        version_number: resolve_option(&self.interner, self.versions.version_numbers[ver_idx]),
611        dependencies: resolve_vec(&self.interner, &self.versions.dependencies[ver_idx]),
612        download_url: resolve_option(&self.interner, self.versions.download_urls[ver_idx]),
613        downloads: self.versions.downloads[ver_idx],
614        date_created: self.versions.dates_created[ver_idx],
615        website_url: resolve_option(&self.interner, self.versions.website_urls[ver_idx]),
616        is_active: self.versions.is_active[ver_idx],
617        uuid4: resolve_option(&self.interner, self.versions.uuid4s[ver_idx]),
618        file_size: self.versions.file_sizes[ver_idx],
619      });
620    }
621
622    Package {
623      name: resolve_option(&self.interner, self.names[idx]),
624      full_name: resolve_option(&self.interner, self.full_names[idx]),
625      owner: resolve_option(&self.interner, self.owners[idx]),
626      package_url: resolve_option(&self.interner, self.package_urls[idx]),
627      date_created: self.dates_created[idx],
628      date_updated: self.dates_updated[idx],
629      uuid4: resolve_option(&self.interner, self.uuid4s[idx]),
630      rating_score: self.rating_scores[idx],
631      is_pinned: self.is_pinned[idx],
632      is_deprecated: self.is_deprecated[idx],
633      has_nsfw_content: self.has_nsfw_content[idx],
634      categories: resolve_vec(&self.interner, &self.categories[idx]),
635      versions,
636    }
637  }
638
639  /// Returns the index of the latest version for a package.
640  ///
641  /// The latest version is determined by comparing the date_created field of all versions.
642  ///
643  /// # Parameters
644  ///
645  /// * `idx` - The index of the package in the manifest
646  ///
647  /// # Returns
648  ///
649  /// The index of the latest version, or None if the package has no versions.
650  pub fn get_latest_version_at(&self, idx: usize) -> Option<usize> {
651    let (ver_start, ver_end) = self.version_ranges[idx];
652
653    if ver_start >= ver_end {
654      return None;
655    }
656
657    (ver_start..ver_end).max_by_key(|&ver_idx| self.versions.dates_created[ver_idx])
658  }
659
660  /// Builds an index mapping full package names to their indices.
661  ///
662  /// This is useful for fast lookups of packages by name.
663  ///
664  /// # Returns
665  ///
666  /// A HashMap mapping resolved full names to package indices.
667  pub fn build_name_index(&self) -> HashMap<String, usize> {
668    self
669      .full_names
670      .iter()
671      .enumerate()
672      .filter_map(|(idx, key)| {
673        let name = resolve_option(&self.interner, *key)?;
674        Some((name, idx))
675      })
676      .collect()
677  }
678
679  /// Validates the internal consistency of the manifest structure.
680  ///
681  /// Checks that all parallel vectors have matching lengths and that version
682  /// ranges are valid.
683  ///
684  /// # Returns
685  ///
686  /// Ok if the manifest is valid, otherwise an error describing the problem.
687  pub fn validate(&self) -> Result<(), String> {
688    let pkg_count = self.names.len();
689
690    if self.full_names.len() != pkg_count {
691      return Err(format!(
692        "full_names length {} != {}",
693        self.full_names.len(),
694        pkg_count
695      ));
696    }
697
698    if self.owners.len() != pkg_count {
699      return Err(format!(
700        "owners length {} != {}",
701        self.owners.len(),
702        pkg_count
703      ));
704    }
705
706    if self.package_urls.len() != pkg_count {
707      return Err(format!(
708        "package_urls length {} != {}",
709        self.package_urls.len(),
710        pkg_count
711      ));
712    }
713
714    if self.dates_created.len() != pkg_count {
715      return Err(format!(
716        "dates_created length {} != {}",
717        self.dates_created.len(),
718        pkg_count
719      ));
720    }
721
722    if self.dates_updated.len() != pkg_count {
723      return Err(format!(
724        "dates_updated length {} != {}",
725        self.dates_updated.len(),
726        pkg_count
727      ));
728    }
729
730    if self.uuid4s.len() != pkg_count {
731      return Err(format!(
732        "uuid4s length {} != {}",
733        self.uuid4s.len(),
734        pkg_count
735      ));
736    }
737
738    if self.rating_scores.len() != pkg_count {
739      return Err(format!(
740        "rating_scores length {} != {}",
741        self.rating_scores.len(),
742        pkg_count
743      ));
744    }
745
746    if self.is_pinned.len() != pkg_count {
747      return Err(format!(
748        "is_pinned length {} != {}",
749        self.is_pinned.len(),
750        pkg_count
751      ));
752    }
753
754    if self.is_deprecated.len() != pkg_count {
755      return Err(format!(
756        "is_deprecated length {} != {}",
757        self.is_deprecated.len(),
758        pkg_count
759      ));
760    }
761
762    if self.has_nsfw_content.len() != pkg_count {
763      return Err(format!(
764        "has_nsfw_content length {} != {}",
765        self.has_nsfw_content.len(),
766        pkg_count
767      ));
768    }
769
770    if self.categories.len() != pkg_count {
771      return Err(format!(
772        "categories length {} != {}",
773        self.categories.len(),
774        pkg_count
775      ));
776    }
777
778    if self.version_ranges.len() != pkg_count {
779      return Err(format!(
780        "version_ranges length {} != {}",
781        self.version_ranges.len(),
782        pkg_count
783      ));
784    }
785
786    let version_count = self.versions.package_indices.len();
787
788    if self.versions.version_numbers.len() != version_count {
789      return Err(format!(
790        "versions.version_numbers length {} != {}",
791        self.versions.version_numbers.len(),
792        version_count
793      ));
794    }
795
796    if self.versions.download_urls.len() != version_count {
797      return Err(format!(
798        "versions.download_urls length {} != {}",
799        self.versions.download_urls.len(),
800        version_count
801      ));
802    }
803
804    if self.versions.dependencies.len() != version_count {
805      return Err(format!(
806        "versions.dependencies length {} != {}",
807        self.versions.dependencies.len(),
808        version_count
809      ));
810    }
811
812    if self.versions.dates_created.len() != version_count {
813      return Err(format!(
814        "versions.dates_created length {} != {}",
815        self.versions.dates_created.len(),
816        version_count
817      ));
818    }
819
820    if self.versions.descriptions.len() != version_count {
821      return Err(format!(
822        "versions.descriptions length {} != {}",
823        self.versions.descriptions.len(),
824        version_count
825      ));
826    }
827
828    if self.versions.icons.len() != version_count {
829      return Err(format!(
830        "versions.icons length {} != {}",
831        self.versions.icons.len(),
832        version_count
833      ));
834    }
835
836    if self.versions.downloads.len() != version_count {
837      return Err(format!(
838        "versions.downloads length {} != {}",
839        self.versions.downloads.len(),
840        version_count
841      ));
842    }
843
844    if self.versions.website_urls.len() != version_count {
845      return Err(format!(
846        "versions.website_urls length {} != {}",
847        self.versions.website_urls.len(),
848        version_count
849      ));
850    }
851
852    if self.versions.is_active.len() != version_count {
853      return Err(format!(
854        "versions.is_active length {} != {}",
855        self.versions.is_active.len(),
856        version_count
857      ));
858    }
859
860    if self.versions.uuid4s.len() != version_count {
861      return Err(format!(
862        "versions.uuid4s length {} != {}",
863        self.versions.uuid4s.len(),
864        version_count
865      ));
866    }
867
868    if self.versions.file_sizes.len() != version_count {
869      return Err(format!(
870        "versions.file_sizes length {} != {}",
871        self.versions.file_sizes.len(),
872        version_count
873      ));
874    }
875
876    for (idx, (start, end)) in self.version_ranges.iter().enumerate() {
877      if start > end {
878        return Err(format!(
879          "Invalid version range at package {}: {} > {}",
880          idx, start, end
881        ));
882      }
883
884      if *end > version_count {
885        return Err(format!(
886          "Version range at package {} ends at {} but only {} versions exist",
887          idx, end, version_count
888        ));
889      }
890    }
891
892    Ok(())
893  }
894}
895
896/// Implementation block for InternedVersionManifest.
897///
898/// Currently empty but reserved for future version-specific methods.
899impl InternedVersionManifest {}
900
901/// Represents a mod package from the Thunderstore API.
902///
903/// This struct contains information about a mod package, including its metadata
904/// and all available versions.
905#[derive(Serialize, Deserialize, Clone)]
906pub struct Package {
907  /// The short name of the package
908  pub name: Option<String>,
909  /// The full name of the package, typically in format "Owner-Name"
910  pub full_name: Option<String>,
911  /// The username of the package owner
912  pub owner: Option<String>,
913  /// The URL to the package's page on Thunderstore
914  pub package_url: Option<String>,
915  /// When the package was first published
916  #[serde(with = "time::serde::rfc3339")]
917  pub date_created: time::OffsetDateTime,
918  /// When the package was last updated
919  #[serde(with = "time::serde::rfc3339")]
920  pub date_updated: time::OffsetDateTime,
921  /// Unique identifier for the package
922  pub uuid4: Option<String>,
923  /// User rating score for the package
924  pub rating_score: Option<u32>,
925  /// Whether the package is pinned by Thunderstore
926  pub is_pinned: Option<bool>,
927  /// Whether the package is marked as deprecated
928  pub is_deprecated: Option<bool>,
929  /// Whether the package contains NSFW content
930  pub has_nsfw_content: Option<bool>,
931  /// List of categories the package belongs to
932  pub categories: Vec<String>,
933  /// All available versions of the package
934  pub versions: Vec<Version>,
935}
936
937/// Represents a specific version of a mod package.
938///
939/// This struct contains information about one version of a package,
940/// including its version number, dependencies, and download information.
941#[derive(Serialize, Deserialize, Debug, Clone)]
942pub struct Version {
943  /// The name of this version
944  pub name: Option<String>,
945  /// The full name of this version
946  pub full_name: Option<String>,
947  /// The description of this version
948  pub description: Option<String>,
949  /// URL to the icon for this version
950  pub icon: Option<String>,
951  /// The version number (e.g., "1.0.0")
952  pub version_number: Option<String>,
953  /// List of dependencies required by this version
954  pub dependencies: Vec<String>,
955  /// URL to download this version
956  pub download_url: Option<String>,
957  /// Number of times this version has been downloaded
958  pub downloads: Option<u32>,
959  /// When this version was published
960  #[serde(with = "time::serde::rfc3339")]
961  pub date_created: time::OffsetDateTime,
962  /// URL to the website for this version
963  pub website_url: Option<String>,
964  /// Whether this version is active
965  pub is_active: Option<bool>,
966  /// Unique identifier for this version
967  pub uuid4: Option<String>,
968  /// Size of the download file in bytes
969  pub file_size: Option<u64>,
970}
971
972impl Package {
973  /// Returns the latest version of the package based on creation date.
974  ///
975  /// # Returns
976  ///
977  /// The most recent version of the package, or `None` if there are no versions.
978  pub fn latest_version(&self) -> Option<&Version> {
979    self
980      .versions
981      .iter()
982      .max_by(|a, b| a.date_created.partial_cmp(&b.date_created).unwrap())
983  }
984
985  /// Constructs a filename and download URL for the latest version of the package.
986  ///
987  /// # Returns
988  ///
989  /// A tuple containing:
990  /// - The formatted zip filename (e.g., "Owner-PackageName-1.0.0.zip")
991  /// - The download URL
992  ///
993  /// Returns `None` if any required information is missing (latest version, download URL, etc.)
994  #[cfg(test)]
995  pub fn zip_and_url(&self) -> Option<(String, String)> {
996    let pkg = self.latest_version()?;
997    let url: String = pkg.download_url.clone()?;
998    let version_number = pkg.version_number.as_ref()?;
999    let package_name = &self.full_name.as_ref()?;
1000    let zip_name = format!("{}-{}.zip", package_name, version_number);
1001
1002    Some((zip_name, url))
1003  }
1004}
1005
1006/// Custom debug implementation for Package to show relevant information.
1007impl std::fmt::Debug for Package {
1008  /// Formats the Package for debugging, including key information like name and dependencies.
1009  ///
1010  /// # Parameters
1011  ///
1012  /// * `f` - The formatter to write to
1013  ///
1014  /// # Returns
1015  ///
1016  /// A Result indicating whether the formatting succeeded
1017  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018    f.debug_struct("Package")
1019      .field("full_name", &self.full_name)
1020      .field(
1021        "dependencies",
1022        &self.latest_version().map(|v| &v.dependencies),
1023      )
1024      .field(
1025        "download_url",
1026        &self.latest_version().map(|v| &v.download_url),
1027      )
1028      .finish()
1029  }
1030}
1031
1032/// Represents a graph of package dependencies to be resolved and installed.
1033///
1034/// This structure helps determine which packages need to be installed,
1035/// including any dependencies they require.
1036#[derive(Debug, Deserialize, Serialize)]
1037pub struct DependencyGraph {
1038  /// The list of package names that the user wants to install
1039  install_packages: Vec<String>,
1040}
1041
1042impl DependencyGraph {
1043  /// Creates a new DependencyGraph with the specified packages to install.
1044  ///
1045  /// # Parameters
1046  ///
1047  /// * `install_packages` - List of package names to install
1048  ///
1049  /// # Returns
1050  ///
1051  /// A new `DependencyGraph` instance
1052  pub fn new(install_packages: Vec<String>) -> Self {
1053    Self { install_packages }
1054  }
1055
1056  /// Resolves all dependencies for the requested packages.
1057  ///
1058  /// This method:
1059  /// 1. Starts with the user-requested packages
1060  /// 2. For each package, adds its dependencies to the list to process
1061  /// 3. Continues until all dependencies are resolved
1062  ///
1063  /// # Parameters
1064  ///
1065  /// * `manifest` - A PackageManifest of all available packages
1066  ///
1067  /// # Returns
1068  ///
1069  /// A HashMap where keys are filenames and values are download URLs for all required packages
1070  #[allow(dead_code)]
1071  pub fn resolve(&self, manifest: &PackageManifest) -> HashMap<String, String> {
1072    let name_index = manifest.build_name_index();
1073    let mut sorted_set: BTreeSet<&str> = self.install_packages.iter().map(String::as_str).collect();
1074    let mut install_indices = Vec::new();
1075
1076    tracing::debug!("Starting with mod list of: {:#?}", sorted_set);
1077
1078    while !sorted_set.is_empty() {
1079      let Some(item) = sorted_set.pop_first() else {
1080        continue;
1081      };
1082
1083      let item = item.split('-').take(2).collect::<Vec<&str>>().join("-");
1084      let Some(&pkg_idx) = name_index.get(&item) else {
1085        continue;
1086      };
1087
1088      if let Some(latest_ver_idx) = manifest.get_latest_version_at(pkg_idx) {
1089        manifest.versions.dependencies[latest_ver_idx]
1090          .iter()
1091          .for_each(|dep| {
1092            let dep_str = dep.as_str();
1093
1094            tracing::debug!(
1095              "Found dependency of {:#?} for {:#?} mod",
1096              dep_str,
1097              manifest.names[pkg_idx].as_ref().unwrap(),
1098            );
1099            sorted_set.insert(dep_str);
1100          });
1101      }
1102
1103      install_indices.push(pkg_idx);
1104    }
1105
1106    Self::package_indices_to_urls(manifest, &install_indices)
1107  }
1108
1109  /// Converts package indices to a HashMap of filenames and download URLs.
1110  ///
1111  /// # Parameters
1112  ///
1113  /// * `manifest` - The PackageManifest
1114  /// * `indices` - Vector of package indices
1115  ///
1116  /// # Returns
1117  ///
1118  /// A HashMap mapping filenames to download URLs
1119  #[allow(dead_code)]
1120  fn package_indices_to_urls(
1121    manifest: &PackageManifest,
1122    indices: &[usize],
1123  ) -> HashMap<String, String> {
1124    indices
1125      .iter()
1126      .filter_map(|&idx| {
1127        let latest_ver_idx = manifest.get_latest_version_at(idx)?;
1128        let url = manifest.versions.download_urls[latest_ver_idx].as_ref()?;
1129        let version_number = manifest.versions.version_numbers[latest_ver_idx].as_ref()?;
1130        let package_name = manifest.full_names[idx].as_ref()?;
1131        let zip_name = format!("{}-{}.zip", package_name, version_number);
1132
1133        Some((zip_name, url.clone()))
1134      })
1135      .collect()
1136  }
1137
1138  /// Resolves all dependencies for the requested packages using an interned manifest.
1139  ///
1140  /// This method:
1141  /// 1. Starts with the user-requested packages
1142  /// 2. For each package, adds its dependencies to the list to process
1143  /// 3. Continues until all dependencies are resolved
1144  ///
1145  /// # Parameters
1146  ///
1147  /// * `manifest` - An InternedPackageManifest of all available packages
1148  ///
1149  /// # Returns
1150  ///
1151  /// A HashMap where keys are filenames and values are download URLs for all required packages
1152  pub fn resolve_interned(&self, manifest: &InternedPackageManifest) -> HashMap<String, String> {
1153    let name_index = manifest.build_name_index();
1154    let mut sorted_set: BTreeSet<String> = self.install_packages.iter().cloned().collect();
1155    let mut install_indices = Vec::new();
1156
1157    tracing::debug!("Starting with mod list of: {:#?}", sorted_set);
1158
1159    while !sorted_set.is_empty() {
1160      let Some(item) = sorted_set.pop_first() else {
1161        continue;
1162      };
1163
1164      let item = item.split('-').take(2).collect::<Vec<&str>>().join("-");
1165      let Some(&pkg_idx) = name_index.get(&item) else {
1166        continue;
1167      };
1168
1169      if let Some(latest_ver_idx) = manifest.get_latest_version_at(pkg_idx) {
1170        let deps = resolve_vec(
1171          &manifest.interner,
1172          &manifest.versions.dependencies[latest_ver_idx],
1173        );
1174
1175        for dep in deps {
1176          tracing::debug!(
1177            "Found dependency of {:#?} for {:#?} mod",
1178            dep,
1179            manifest.resolve_name_at(pkg_idx).unwrap_or_default(),
1180          );
1181          sorted_set.insert(dep);
1182        }
1183      }
1184
1185      install_indices.push(pkg_idx);
1186    }
1187
1188    Self::package_indices_to_urls_interned(manifest, &install_indices)
1189  }
1190
1191  /// Converts package indices to a HashMap of filenames and download URLs using an interned manifest.
1192  ///
1193  /// # Parameters
1194  ///
1195  /// * `manifest` - The InternedPackageManifest
1196  /// * `indices` - Vector of package indices
1197  ///
1198  /// # Returns
1199  ///
1200  /// A HashMap mapping filenames to download URLs
1201  fn package_indices_to_urls_interned(
1202    manifest: &InternedPackageManifest,
1203    indices: &[usize],
1204  ) -> HashMap<String, String> {
1205    indices
1206      .iter()
1207      .filter_map(|&idx| {
1208        let latest_ver_idx = manifest.get_latest_version_at(idx)?;
1209        let url = resolve_option(
1210          &manifest.interner,
1211          manifest.versions.download_urls[latest_ver_idx],
1212        )?;
1213        let version_number = resolve_option(
1214          &manifest.interner,
1215          manifest.versions.version_numbers[latest_ver_idx],
1216        )?;
1217        let package_name = manifest.resolve_full_name_at(idx)?;
1218        let zip_name = format!("{}-{}.zip", package_name, version_number);
1219
1220        Some((zip_name, url))
1221      })
1222      .collect()
1223  }
1224}
1225
1226impl From<Vec<Package>> for PackageManifest {
1227  fn from(packages: Vec<Package>) -> Self {
1228    let num_packages = packages.len();
1229
1230    let total_versions: usize = packages.iter().map(|p| p.versions.len()).sum();
1231
1232    let mut names = Vec::with_capacity(num_packages);
1233    let mut full_names = Vec::with_capacity(num_packages);
1234    let mut owners = Vec::with_capacity(num_packages);
1235    let mut package_urls = Vec::with_capacity(num_packages);
1236    let mut dates_created = Vec::with_capacity(num_packages);
1237    let mut dates_updated = Vec::with_capacity(num_packages);
1238    let mut uuid4s = Vec::with_capacity(num_packages);
1239    let mut rating_scores = Vec::with_capacity(num_packages);
1240    let mut is_pinned = Vec::with_capacity(num_packages);
1241    let mut is_deprecated = Vec::with_capacity(num_packages);
1242    let mut has_nsfw_content = Vec::with_capacity(num_packages);
1243    let mut categories = Vec::with_capacity(num_packages);
1244    let mut version_ranges = Vec::with_capacity(num_packages);
1245
1246    let mut package_indices = Vec::with_capacity(total_versions);
1247    let mut version_numbers = Vec::with_capacity(total_versions);
1248    let mut download_urls = Vec::with_capacity(total_versions);
1249    let mut dependencies = Vec::with_capacity(total_versions);
1250    let mut version_dates_created = Vec::with_capacity(total_versions);
1251    let mut descriptions = Vec::with_capacity(total_versions);
1252    let mut icons = Vec::with_capacity(total_versions);
1253    let mut downloads = Vec::with_capacity(total_versions);
1254    let mut website_urls = Vec::with_capacity(total_versions);
1255    let mut is_active = Vec::with_capacity(total_versions);
1256    let mut version_uuid4s = Vec::with_capacity(total_versions);
1257    let mut file_sizes = Vec::with_capacity(total_versions);
1258
1259    let mut version_offset = 0;
1260
1261    for (pkg_idx, package) in packages.into_iter().enumerate() {
1262      names.push(package.name);
1263      full_names.push(package.full_name);
1264      owners.push(package.owner);
1265      package_urls.push(package.package_url);
1266      dates_created.push(package.date_created);
1267      dates_updated.push(package.date_updated);
1268      uuid4s.push(package.uuid4);
1269      rating_scores.push(package.rating_score);
1270      is_pinned.push(package.is_pinned);
1271      is_deprecated.push(package.is_deprecated);
1272      has_nsfw_content.push(package.has_nsfw_content);
1273      categories.push(package.categories);
1274
1275      let version_count = package.versions.len();
1276      version_ranges.push((version_offset, version_offset + version_count));
1277
1278      for version in package.versions {
1279        package_indices.push(pkg_idx);
1280        version_numbers.push(version.version_number);
1281        download_urls.push(version.download_url);
1282        dependencies.push(version.dependencies);
1283        version_dates_created.push(version.date_created);
1284        descriptions.push(version.description);
1285        icons.push(version.icon);
1286        downloads.push(version.downloads);
1287        website_urls.push(version.website_url);
1288        is_active.push(version.is_active);
1289        version_uuid4s.push(version.uuid4);
1290        file_sizes.push(version.file_size);
1291      }
1292
1293      version_offset += version_count;
1294    }
1295
1296    PackageManifest {
1297      names,
1298      full_names,
1299      owners,
1300      package_urls,
1301      dates_created,
1302      dates_updated,
1303      uuid4s,
1304      rating_scores,
1305      is_pinned,
1306      is_deprecated,
1307      has_nsfw_content,
1308      categories,
1309      version_ranges,
1310      versions: VersionManifest {
1311        package_indices,
1312        version_numbers,
1313        download_urls,
1314        dependencies,
1315        dates_created: version_dates_created,
1316        descriptions,
1317        icons,
1318        downloads,
1319        website_urls,
1320        is_active,
1321        uuid4s: version_uuid4s,
1322        file_sizes,
1323      },
1324    }
1325  }
1326}
1327
1328impl From<Vec<Package>> for InternedPackageManifest {
1329  fn from(packages: Vec<Package>) -> Self {
1330    let num_packages = packages.len();
1331    let total_versions: usize = packages.iter().map(|p| p.versions.len()).sum();
1332
1333    let mut interner = StringInterner::default();
1334
1335    let mut names = Vec::with_capacity(num_packages);
1336    let mut full_names = Vec::with_capacity(num_packages);
1337    let mut owners = Vec::with_capacity(num_packages);
1338    let mut package_urls = Vec::with_capacity(num_packages);
1339    let mut dates_created = Vec::with_capacity(num_packages);
1340    let mut dates_updated = Vec::with_capacity(num_packages);
1341    let mut uuid4s = Vec::with_capacity(num_packages);
1342    let mut rating_scores = Vec::with_capacity(num_packages);
1343    let mut is_pinned = Vec::with_capacity(num_packages);
1344    let mut is_deprecated = Vec::with_capacity(num_packages);
1345    let mut has_nsfw_content = Vec::with_capacity(num_packages);
1346    let mut categories = Vec::with_capacity(num_packages);
1347    let mut version_ranges = Vec::with_capacity(num_packages);
1348
1349    let mut package_indices = Vec::with_capacity(total_versions);
1350    let mut version_numbers = Vec::with_capacity(total_versions);
1351    let mut download_urls = Vec::with_capacity(total_versions);
1352    let mut dependencies = Vec::with_capacity(total_versions);
1353    let mut version_dates_created = Vec::with_capacity(total_versions);
1354    let mut descriptions = Vec::with_capacity(total_versions);
1355    let mut icons = Vec::with_capacity(total_versions);
1356    let mut downloads = Vec::with_capacity(total_versions);
1357    let mut website_urls = Vec::with_capacity(total_versions);
1358    let mut is_active = Vec::with_capacity(total_versions);
1359    let mut version_uuid4s = Vec::with_capacity(total_versions);
1360    let mut file_sizes = Vec::with_capacity(total_versions);
1361
1362    let mut version_offset = 0;
1363
1364    for (pkg_idx, package) in packages.into_iter().enumerate() {
1365      names.push(intern_option(&mut interner, package.name.as_deref()));
1366      full_names.push(intern_option(&mut interner, package.full_name.as_deref()));
1367      owners.push(intern_option(&mut interner, package.owner.as_deref()));
1368      package_urls.push(intern_option(&mut interner, package.package_url.as_deref()));
1369      dates_created.push(package.date_created);
1370      dates_updated.push(package.date_updated);
1371      uuid4s.push(intern_option(&mut interner, package.uuid4.as_deref()));
1372      rating_scores.push(package.rating_score);
1373      is_pinned.push(package.is_pinned);
1374      is_deprecated.push(package.is_deprecated);
1375      has_nsfw_content.push(package.has_nsfw_content);
1376      categories.push(intern_vec(&mut interner, &package.categories));
1377
1378      let version_count = package.versions.len();
1379      version_ranges.push((version_offset, version_offset + version_count));
1380
1381      for version in package.versions {
1382        package_indices.push(pkg_idx);
1383        version_numbers.push(intern_option(
1384          &mut interner,
1385          version.version_number.as_deref(),
1386        ));
1387        download_urls.push(intern_option(
1388          &mut interner,
1389          version.download_url.as_deref(),
1390        ));
1391        dependencies.push(intern_vec(&mut interner, &version.dependencies));
1392        version_dates_created.push(version.date_created);
1393        descriptions.push(intern_option(&mut interner, version.description.as_deref()));
1394        icons.push(intern_option(&mut interner, version.icon.as_deref()));
1395        downloads.push(version.downloads);
1396        website_urls.push(intern_option(&mut interner, version.website_url.as_deref()));
1397        is_active.push(version.is_active);
1398        version_uuid4s.push(intern_option(&mut interner, version.uuid4.as_deref()));
1399        file_sizes.push(version.file_size);
1400      }
1401
1402      version_offset += version_count;
1403    }
1404
1405    InternedPackageManifest {
1406      interner,
1407      names,
1408      full_names,
1409      owners,
1410      package_urls,
1411      dates_created,
1412      dates_updated,
1413      uuid4s,
1414      rating_scores,
1415      is_pinned,
1416      is_deprecated,
1417      has_nsfw_content,
1418      categories,
1419      version_ranges,
1420      versions: InternedVersionManifest {
1421        package_indices,
1422        version_numbers,
1423        download_urls,
1424        dependencies,
1425        dates_created: version_dates_created,
1426        descriptions,
1427        icons,
1428        downloads,
1429        website_urls,
1430        is_active,
1431        uuid4s: version_uuid4s,
1432        file_sizes,
1433      },
1434    }
1435  }
1436}
1437
1438impl From<PackageManifest> for InternedPackageManifest {
1439  fn from(manifest: PackageManifest) -> Self {
1440    let mut interner = StringInterner::default();
1441
1442    let names: Vec<Option<InternKey>> = manifest
1443      .names
1444      .iter()
1445      .map(|s| intern_option(&mut interner, s.as_deref()))
1446      .collect();
1447
1448    let full_names: Vec<Option<InternKey>> = manifest
1449      .full_names
1450      .iter()
1451      .map(|s| intern_option(&mut interner, s.as_deref()))
1452      .collect();
1453
1454    let owners: Vec<Option<InternKey>> = manifest
1455      .owners
1456      .iter()
1457      .map(|s| intern_option(&mut interner, s.as_deref()))
1458      .collect();
1459
1460    let package_urls: Vec<Option<InternKey>> = manifest
1461      .package_urls
1462      .iter()
1463      .map(|s| intern_option(&mut interner, s.as_deref()))
1464      .collect();
1465
1466    let uuid4s: Vec<Option<InternKey>> = manifest
1467      .uuid4s
1468      .iter()
1469      .map(|s| intern_option(&mut interner, s.as_deref()))
1470      .collect();
1471
1472    let categories: Vec<Vec<InternKey>> = manifest
1473      .categories
1474      .iter()
1475      .map(|c| intern_vec(&mut interner, c))
1476      .collect();
1477
1478    let version_numbers: Vec<Option<InternKey>> = manifest
1479      .versions
1480      .version_numbers
1481      .iter()
1482      .map(|s| intern_option(&mut interner, s.as_deref()))
1483      .collect();
1484
1485    let download_urls: Vec<Option<InternKey>> = manifest
1486      .versions
1487      .download_urls
1488      .iter()
1489      .map(|s| intern_option(&mut interner, s.as_deref()))
1490      .collect();
1491
1492    let dependencies: Vec<Vec<InternKey>> = manifest
1493      .versions
1494      .dependencies
1495      .iter()
1496      .map(|d| intern_vec(&mut interner, d))
1497      .collect();
1498
1499    let descriptions: Vec<Option<InternKey>> = manifest
1500      .versions
1501      .descriptions
1502      .iter()
1503      .map(|s| intern_option(&mut interner, s.as_deref()))
1504      .collect();
1505
1506    let icons: Vec<Option<InternKey>> = manifest
1507      .versions
1508      .icons
1509      .iter()
1510      .map(|s| intern_option(&mut interner, s.as_deref()))
1511      .collect();
1512
1513    let website_urls: Vec<Option<InternKey>> = manifest
1514      .versions
1515      .website_urls
1516      .iter()
1517      .map(|s| intern_option(&mut interner, s.as_deref()))
1518      .collect();
1519
1520    let version_uuid4s: Vec<Option<InternKey>> = manifest
1521      .versions
1522      .uuid4s
1523      .iter()
1524      .map(|s| intern_option(&mut interner, s.as_deref()))
1525      .collect();
1526
1527    InternedPackageManifest {
1528      interner,
1529      names,
1530      full_names,
1531      owners,
1532      package_urls,
1533      dates_created: manifest.dates_created,
1534      dates_updated: manifest.dates_updated,
1535      uuid4s,
1536      rating_scores: manifest.rating_scores,
1537      is_pinned: manifest.is_pinned,
1538      is_deprecated: manifest.is_deprecated,
1539      has_nsfw_content: manifest.has_nsfw_content,
1540      categories,
1541      version_ranges: manifest.version_ranges,
1542      versions: InternedVersionManifest {
1543        package_indices: manifest.versions.package_indices,
1544        version_numbers,
1545        download_urls,
1546        dependencies,
1547        dates_created: manifest.versions.dates_created,
1548        descriptions,
1549        icons,
1550        downloads: manifest.versions.downloads,
1551        website_urls,
1552        is_active: manifest.versions.is_active,
1553        uuid4s: version_uuid4s,
1554        file_sizes: manifest.versions.file_sizes,
1555      },
1556    }
1557  }
1558}
1559
1560/// Converts an InternKey to its u32 index representation for serialization.
1561///
1562/// # Parameters
1563///
1564/// * `_interner` - The StringInterner (unused but kept for API symmetry)
1565/// * `key` - The InternKey to convert
1566///
1567/// # Returns
1568///
1569/// The u32 index corresponding to the key
1570fn key_to_index(_interner: &StringInterner, key: InternKey) -> u32 {
1571  key.into_usize() as u32
1572}
1573
1574/// Converts a u32 index back to an InternKey for deserialization.
1575///
1576/// # Parameters
1577///
1578/// * `index` - The u32 index to convert
1579///
1580/// # Returns
1581///
1582/// The InternKey corresponding to the index
1583///
1584/// # Panics
1585///
1586/// Panics if the index cannot be converted to a valid InternKey
1587fn index_to_key(index: u32) -> InternKey {
1588  InternKey::try_from_usize(index as usize).expect("Invalid intern key index")
1589}
1590
1591impl From<&InternedPackageManifest> for SerializableInternedManifest {
1592  fn from(manifest: &InternedPackageManifest) -> Self {
1593    let string_table: Vec<String> = manifest.interner.strings().map(|s| s.to_string()).collect();
1594
1595    let names: Vec<Option<u32>> = manifest
1596      .names
1597      .iter()
1598      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1599      .collect();
1600
1601    let full_names: Vec<Option<u32>> = manifest
1602      .full_names
1603      .iter()
1604      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1605      .collect();
1606
1607    let owners: Vec<Option<u32>> = manifest
1608      .owners
1609      .iter()
1610      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1611      .collect();
1612
1613    let package_urls: Vec<Option<u32>> = manifest
1614      .package_urls
1615      .iter()
1616      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1617      .collect();
1618
1619    let uuid4s: Vec<Option<u32>> = manifest
1620      .uuid4s
1621      .iter()
1622      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1623      .collect();
1624
1625    let categories: Vec<Vec<u32>> = manifest
1626      .categories
1627      .iter()
1628      .map(|keys| {
1629        keys
1630          .iter()
1631          .map(|k| key_to_index(&manifest.interner, *k))
1632          .collect()
1633      })
1634      .collect();
1635
1636    let version_numbers: Vec<Option<u32>> = manifest
1637      .versions
1638      .version_numbers
1639      .iter()
1640      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1641      .collect();
1642
1643    let download_urls: Vec<Option<u32>> = manifest
1644      .versions
1645      .download_urls
1646      .iter()
1647      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1648      .collect();
1649
1650    let dependencies: Vec<Vec<u32>> = manifest
1651      .versions
1652      .dependencies
1653      .iter()
1654      .map(|keys| {
1655        keys
1656          .iter()
1657          .map(|k| key_to_index(&manifest.interner, *k))
1658          .collect()
1659      })
1660      .collect();
1661
1662    let descriptions: Vec<Option<u32>> = manifest
1663      .versions
1664      .descriptions
1665      .iter()
1666      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1667      .collect();
1668
1669    let icons: Vec<Option<u32>> = manifest
1670      .versions
1671      .icons
1672      .iter()
1673      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1674      .collect();
1675
1676    let website_urls: Vec<Option<u32>> = manifest
1677      .versions
1678      .website_urls
1679      .iter()
1680      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1681      .collect();
1682
1683    let version_uuid4s: Vec<Option<u32>> = manifest
1684      .versions
1685      .uuid4s
1686      .iter()
1687      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
1688      .collect();
1689
1690    SerializableInternedManifest {
1691      string_table,
1692      names,
1693      full_names,
1694      owners,
1695      package_urls,
1696      dates_created: manifest.dates_created.clone(),
1697      dates_updated: manifest.dates_updated.clone(),
1698      uuid4s,
1699      rating_scores: manifest.rating_scores.clone(),
1700      is_pinned: manifest.is_pinned.clone(),
1701      is_deprecated: manifest.is_deprecated.clone(),
1702      has_nsfw_content: manifest.has_nsfw_content.clone(),
1703      categories,
1704      version_ranges: manifest.version_ranges.clone(),
1705      versions: SerializableInternedVersionManifest {
1706        package_indices: manifest.versions.package_indices.clone(),
1707        version_numbers,
1708        download_urls,
1709        dependencies,
1710        dates_created: manifest.versions.dates_created.clone(),
1711        descriptions,
1712        icons,
1713        downloads: manifest.versions.downloads.clone(),
1714        website_urls,
1715        is_active: manifest.versions.is_active.clone(),
1716        uuid4s: version_uuid4s,
1717        file_sizes: manifest.versions.file_sizes.clone(),
1718      },
1719    }
1720  }
1721}
1722
1723impl From<SerializableInternedManifest> for InternedPackageManifest {
1724  fn from(manifest: SerializableInternedManifest) -> Self {
1725    let mut interner = StringInterner::default();
1726
1727    for s in &manifest.string_table {
1728      interner.get_or_intern(s);
1729    }
1730
1731    let names: Vec<Option<InternKey>> = manifest
1732      .names
1733      .iter()
1734      .map(|idx| idx.map(index_to_key))
1735      .collect();
1736
1737    let full_names: Vec<Option<InternKey>> = manifest
1738      .full_names
1739      .iter()
1740      .map(|idx| idx.map(index_to_key))
1741      .collect();
1742
1743    let owners: Vec<Option<InternKey>> = manifest
1744      .owners
1745      .iter()
1746      .map(|idx| idx.map(index_to_key))
1747      .collect();
1748
1749    let package_urls: Vec<Option<InternKey>> = manifest
1750      .package_urls
1751      .iter()
1752      .map(|idx| idx.map(index_to_key))
1753      .collect();
1754
1755    let uuid4s: Vec<Option<InternKey>> = manifest
1756      .uuid4s
1757      .iter()
1758      .map(|idx| idx.map(index_to_key))
1759      .collect();
1760
1761    let categories: Vec<Vec<InternKey>> = manifest
1762      .categories
1763      .iter()
1764      .map(|indices| indices.iter().map(|&i| index_to_key(i)).collect())
1765      .collect();
1766
1767    let version_numbers: Vec<Option<InternKey>> = manifest
1768      .versions
1769      .version_numbers
1770      .iter()
1771      .map(|idx| idx.map(index_to_key))
1772      .collect();
1773
1774    let download_urls: Vec<Option<InternKey>> = manifest
1775      .versions
1776      .download_urls
1777      .iter()
1778      .map(|idx| idx.map(index_to_key))
1779      .collect();
1780
1781    let dependencies: Vec<Vec<InternKey>> = manifest
1782      .versions
1783      .dependencies
1784      .iter()
1785      .map(|indices| indices.iter().map(|&i| index_to_key(i)).collect())
1786      .collect();
1787
1788    let descriptions: Vec<Option<InternKey>> = manifest
1789      .versions
1790      .descriptions
1791      .iter()
1792      .map(|idx| idx.map(index_to_key))
1793      .collect();
1794
1795    let icons: Vec<Option<InternKey>> = manifest
1796      .versions
1797      .icons
1798      .iter()
1799      .map(|idx| idx.map(index_to_key))
1800      .collect();
1801
1802    let website_urls: Vec<Option<InternKey>> = manifest
1803      .versions
1804      .website_urls
1805      .iter()
1806      .map(|idx| idx.map(index_to_key))
1807      .collect();
1808
1809    let version_uuid4s: Vec<Option<InternKey>> = manifest
1810      .versions
1811      .uuid4s
1812      .iter()
1813      .map(|idx| idx.map(index_to_key))
1814      .collect();
1815
1816    InternedPackageManifest {
1817      interner,
1818      names,
1819      full_names,
1820      owners,
1821      package_urls,
1822      dates_created: manifest.dates_created,
1823      dates_updated: manifest.dates_updated,
1824      uuid4s,
1825      rating_scores: manifest.rating_scores,
1826      is_pinned: manifest.is_pinned,
1827      is_deprecated: manifest.is_deprecated,
1828      has_nsfw_content: manifest.has_nsfw_content,
1829      categories,
1830      version_ranges: manifest.version_ranges,
1831      versions: InternedVersionManifest {
1832        package_indices: manifest.versions.package_indices,
1833        version_numbers,
1834        download_urls,
1835        dependencies,
1836        dates_created: manifest.versions.dates_created,
1837        descriptions,
1838        icons,
1839        downloads: manifest.versions.downloads,
1840        website_urls,
1841        is_active: manifest.versions.is_active,
1842        uuid4s: version_uuid4s,
1843        file_sizes: manifest.versions.file_sizes,
1844      },
1845    }
1846  }
1847}
1848
1849#[cfg(test)]
1850mod tests {
1851  use super::*;
1852  use time::OffsetDateTime;
1853
1854  fn create_test_package(name: &str, owner: &str, version: &str) -> Package {
1855    let full_name = format!("{}-{}", owner, name);
1856    Package {
1857      name: Some(name.to_string()),
1858      full_name: Some(full_name.clone()),
1859      owner: Some(owner.to_string()),
1860      package_url: Some(format!("https://example.com/{}", name)),
1861      date_created: OffsetDateTime::now_utc(),
1862      date_updated: OffsetDateTime::now_utc(),
1863      uuid4: Some("test-uuid".to_string()),
1864      rating_score: Some(5),
1865      is_pinned: Some(false),
1866      is_deprecated: Some(false),
1867      has_nsfw_content: Some(false),
1868      categories: vec!["category1".to_string()],
1869      versions: vec![Version {
1870        name: Some(name.to_string()),
1871        full_name: Some(full_name.clone()),
1872        description: Some("Test description".to_string()),
1873        icon: Some("icon.png".to_string()),
1874        version_number: Some(version.to_string()),
1875        dependencies: vec![],
1876        download_url: Some(format!("https://example.com/{}/download", name)),
1877        downloads: Some(100),
1878        date_created: OffsetDateTime::now_utc(),
1879        website_url: Some("https://example.com".to_string()),
1880        is_active: Some(true),
1881        uuid4: Some("test-version-uuid".to_string()),
1882        file_size: Some(1024),
1883      }],
1884    }
1885  }
1886
1887  fn create_test_package_with_dependencies(
1888    name: &str,
1889    owner: &str,
1890    version: &str,
1891    dependencies: Vec<String>,
1892  ) -> Package {
1893    let mut pkg = create_test_package(name, owner, version);
1894    if let Some(latest_version) = pkg.versions.first_mut() {
1895      latest_version.dependencies = dependencies;
1896    }
1897    pkg
1898  }
1899
1900  #[test]
1901  fn test_latest_version() {
1902    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1903
1904    let older_version = Version {
1905      name: Some("TestMod".to_string()),
1906      full_name: Some("TestOwner-TestMod".to_string()),
1907      description: Some("Older version".to_string()),
1908      icon: Some("icon.png".to_string()),
1909      version_number: Some("0.9.0".to_string()),
1910      dependencies: vec![],
1911      download_url: Some("https://example.com/TestMod/download-old".to_string()),
1912      downloads: Some(50),
1913      date_created: OffsetDateTime::now_utc().saturating_sub(time::Duration::days(30)),
1914      website_url: Some("https://example.com".to_string()),
1915      is_active: Some(true),
1916      uuid4: Some("old-version-uuid".to_string()),
1917      file_size: Some(512),
1918    };
1919    pkg.versions.push(older_version);
1920
1921    let newer_version = Version {
1922      name: Some("TestMod".to_string()),
1923      full_name: Some("TestOwner-TestMod".to_string()),
1924      description: Some("Newer version".to_string()),
1925      icon: Some("icon.png".to_string()),
1926      version_number: Some("1.1.0".to_string()),
1927      dependencies: vec![],
1928      download_url: Some("https://example.com/TestMod/download-new".to_string()),
1929      downloads: Some(150),
1930      date_created: OffsetDateTime::now_utc().saturating_add(time::Duration::days(30)),
1931      website_url: Some("https://example.com".to_string()),
1932      is_active: Some(true),
1933      uuid4: Some("new-version-uuid".to_string()),
1934      file_size: Some(2048),
1935    };
1936    pkg.versions.push(newer_version);
1937
1938    let latest = pkg.latest_version().unwrap();
1939    assert_eq!(latest.version_number, Some("1.1.0".to_string()));
1940    assert_eq!(latest.description, Some("Newer version".to_string()));
1941  }
1942
1943  #[test]
1944  fn test_zip_and_url() {
1945    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1946    let (filename, url) = pkg.zip_and_url().unwrap();
1947
1948    assert_eq!(filename, "TestOwner-TestMod-1.0.0.zip");
1949    assert_eq!(url, "https://example.com/TestMod/download");
1950  }
1951
1952  #[test]
1953  fn test_zip_and_url_missing_data() {
1954    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1955
1956    if let Some(version) = pkg.versions.first_mut() {
1957      version.version_number = None;
1958    }
1959
1960    assert!(pkg.zip_and_url().is_none());
1961
1962    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1963
1964    if let Some(version) = pkg.versions.first_mut() {
1965      version.download_url = None;
1966    }
1967
1968    assert!(pkg.zip_and_url().is_none());
1969
1970    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1971    pkg.full_name = None;
1972
1973    assert!(pkg.zip_and_url().is_none());
1974  }
1975
1976  #[test]
1977  fn test_dependency_graph_resolve() {
1978    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
1979    let pkg2 = create_test_package_with_dependencies(
1980      "ModB",
1981      "Owner2",
1982      "2.0.0",
1983      vec!["Owner3-ModC".to_string()],
1984    );
1985    let pkg3 = create_test_package_with_dependencies(
1986      "ModC",
1987      "Owner3",
1988      "1.5.0",
1989      vec!["Owner4-ModD".to_string()],
1990    );
1991    let pkg4 = create_test_package("ModD", "Owner4", "0.9.0");
1992
1993    let packages = vec![pkg1, pkg2, pkg3, pkg4];
1994    let manifest: PackageManifest = packages.into();
1995
1996    let dg1 = DependencyGraph::new(vec!["Owner1-ModA".to_string()]);
1997    let result1 = dg1.resolve(&manifest);
1998    assert_eq!(result1.len(), 1);
1999    assert!(result1.contains_key("Owner1-ModA-1.0.0.zip"));
2000
2001    let dg2 = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
2002    let result2 = dg2.resolve(&manifest);
2003    assert_eq!(result2.len(), 3);
2004    assert!(result2.contains_key("Owner2-ModB-2.0.0.zip"));
2005    assert!(result2.contains_key("Owner3-ModC-1.5.0.zip"));
2006    assert!(result2.contains_key("Owner4-ModD-0.9.0.zip"));
2007
2008    let dg3 = DependencyGraph::new(vec!["Owner1-ModA".to_string(), "Owner3-ModC".to_string()]);
2009    let result3 = dg3.resolve(&manifest);
2010    assert_eq!(result3.len(), 3);
2011    assert!(result3.contains_key("Owner1-ModA-1.0.0.zip"));
2012    assert!(result3.contains_key("Owner3-ModC-1.5.0.zip"));
2013    assert!(result3.contains_key("Owner4-ModD-0.9.0.zip"));
2014
2015    let dg4 = DependencyGraph::new(vec!["Owner5-ModE".to_string()]);
2016    let result4 = dg4.resolve(&manifest);
2017    assert_eq!(result4.len(), 0);
2018  }
2019
2020  #[test]
2021  fn test_package_debug() {
2022    let pkg = create_test_package("ModTest", "OwnerTest", "1.2.3");
2023    let debug_output = format!("{:?}", pkg);
2024
2025    assert!(debug_output.contains("Package"));
2026    assert!(debug_output.contains("full_name: Some(\"OwnerTest-ModTest\")"));
2027    assert!(debug_output.contains("dependencies: Some([])"));
2028    assert!(
2029      debug_output.contains("download_url: Some(Some(\"https://example.com/ModTest/download\"))")
2030    );
2031
2032    let mut pkg_with_deps = create_test_package_with_dependencies(
2033      "ModWithDeps",
2034      "OwnerTest",
2035      "2.0.0",
2036      vec!["Dep1-Mod1".to_string(), "Dep2-Mod2".to_string()],
2037    );
2038    let debug_with_deps = format!("{:?}", pkg_with_deps);
2039
2040    assert!(debug_with_deps.contains("dependencies: Some([\"Dep1-Mod1\", \"Dep2-Mod2\"])"));
2041
2042    pkg_with_deps.full_name = None;
2043    let debug_missing_name = format!("{:?}", pkg_with_deps);
2044    assert!(debug_missing_name.contains("full_name: None"));
2045
2046    let mut pkg_no_version = create_test_package("NoVersion", "TestOwner", "1.0.0");
2047    pkg_no_version.versions.clear();
2048    let debug_no_version = format!("{:?}", pkg_no_version);
2049
2050    assert!(debug_no_version.contains("dependencies: None"));
2051    assert!(debug_no_version.contains("download_url: None"));
2052  }
2053
2054  #[test]
2055  fn test_package_to_manifest_conversion() {
2056    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2057    let pkg2 = create_test_package_with_dependencies(
2058      "ModB",
2059      "Owner2",
2060      "2.0.0",
2061      vec!["Owner3-ModC".to_string()],
2062    );
2063
2064    let packages = vec![pkg1, pkg2];
2065    let manifest: PackageManifest = packages.clone().into();
2066
2067    assert_eq!(manifest.len(), 2);
2068    assert_eq!(manifest.names[0], Some("ModA".to_string()));
2069    assert_eq!(manifest.names[1], Some("ModB".to_string()));
2070    assert_eq!(manifest.full_names[0], Some("Owner1-ModA".to_string()));
2071    assert_eq!(manifest.full_names[1], Some("Owner2-ModB".to_string()));
2072
2073    assert_eq!(manifest.versions.version_numbers.len(), 2);
2074
2075    assert_eq!(manifest.version_ranges[0], (0, 1));
2076    assert_eq!(manifest.version_ranges[1], (1, 2));
2077
2078    assert_eq!(
2079      manifest.versions.version_numbers[0],
2080      Some("1.0.0".to_string())
2081    );
2082    assert_eq!(
2083      manifest.versions.version_numbers[1],
2084      Some("2.0.0".to_string())
2085    );
2086
2087    assert_eq!(manifest.versions.dependencies[0], Vec::<String>::new());
2088    assert_eq!(
2089      manifest.versions.dependencies[1],
2090      vec!["Owner3-ModC".to_string()]
2091    );
2092  }
2093
2094  #[test]
2095  fn test_manifest_get_package_methods() {
2096    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2097    let pkg2 = create_test_package_with_dependencies(
2098      "ModB",
2099      "Owner2",
2100      "2.0.0",
2101      vec!["Owner3-ModC".to_string()],
2102    );
2103
2104    let original_packages = vec![pkg1, pkg2];
2105    let manifest: PackageManifest = original_packages.clone().into();
2106
2107    assert_eq!(manifest.len(), 2);
2108
2109    let pkg_a = manifest.get_package_by_full_name("Owner1-ModA").unwrap();
2110    assert_eq!(pkg_a.name, Some("ModA".to_string()));
2111    assert_eq!(pkg_a.owner, Some("Owner1".to_string()));
2112    assert_eq!(pkg_a.versions.len(), 1);
2113    assert_eq!(pkg_a.versions[0].version_number, Some("1.0.0".to_string()));
2114
2115    let pkg_b = manifest.get_package_by_full_name("Owner2-ModB").unwrap();
2116    assert_eq!(pkg_b.name, Some("ModB".to_string()));
2117    assert_eq!(pkg_b.owner, Some("Owner2".to_string()));
2118    assert_eq!(pkg_b.versions.len(), 1);
2119    assert_eq!(pkg_b.versions[0].version_number, Some("2.0.0".to_string()));
2120    assert_eq!(
2121      pkg_b.versions[0].dependencies,
2122      vec!["Owner3-ModC".to_string()]
2123    );
2124
2125    let idx = manifest.find_index_by_full_name("Owner1-ModA").unwrap();
2126    assert_eq!(manifest.names[idx], Some("ModA".to_string()));
2127  }
2128
2129  #[test]
2130  fn test_round_trip_conversion() {
2131    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2132
2133    pkg.versions.push(Version {
2134      name: Some("TestMod".to_string()),
2135      full_name: Some("TestOwner-TestMod".to_string()),
2136      description: Some("Version 2".to_string()),
2137      icon: Some("icon2.png".to_string()),
2138      version_number: Some("2.0.0".to_string()),
2139      dependencies: vec!["Dep1-Mod1".to_string()],
2140      download_url: Some("https://example.com/TestMod/download-v2".to_string()),
2141      downloads: Some(200),
2142      date_created: OffsetDateTime::now_utc(),
2143      website_url: Some("https://example.com".to_string()),
2144      is_active: Some(true),
2145      uuid4: Some("test-version-uuid-2".to_string()),
2146      file_size: Some(2048),
2147    });
2148
2149    let original_full_name = pkg.full_name.clone().unwrap();
2150    let original_version_count = pkg.versions.len();
2151
2152    let packages = vec![pkg];
2153    let manifest: PackageManifest = packages.into();
2154
2155    let reconstructed = manifest
2156      .get_package_by_full_name(&original_full_name)
2157      .unwrap();
2158    assert_eq!(reconstructed.full_name, Some(original_full_name));
2159    assert_eq!(reconstructed.versions.len(), original_version_count);
2160    assert_eq!(
2161      reconstructed.versions[0].version_number,
2162      Some("1.0.0".to_string())
2163    );
2164    assert_eq!(
2165      reconstructed.versions[1].version_number,
2166      Some("2.0.0".to_string())
2167    );
2168    assert_eq!(
2169      reconstructed.versions[1].dependencies,
2170      vec!["Dep1-Mod1".to_string()]
2171    );
2172  }
2173
2174  #[test]
2175  fn test_validate_valid_manifest() {
2176    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2177    let packages = vec![pkg];
2178    let manifest: PackageManifest = packages.into();
2179
2180    assert!(manifest.validate().is_ok());
2181  }
2182
2183  #[test]
2184  fn test_validate_mismatched_full_names_length() {
2185    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2186    let packages = vec![pkg];
2187    let mut manifest: PackageManifest = packages.into();
2188
2189    manifest.full_names.pop();
2190
2191    let result = manifest.validate();
2192    assert!(result.is_err());
2193    assert!(result.unwrap_err().contains("full_names length"));
2194  }
2195
2196  #[test]
2197  fn test_validate_mismatched_owners_length() {
2198    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2199    let packages = vec![pkg];
2200    let mut manifest: PackageManifest = packages.into();
2201
2202    manifest.owners.push(Some("Extra".to_string()));
2203
2204    let result = manifest.validate();
2205    assert!(result.is_err());
2206    assert!(result.unwrap_err().contains("owners length"));
2207  }
2208
2209  #[test]
2210  fn test_validate_invalid_version_range() {
2211    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2212    let packages = vec![pkg];
2213    let mut manifest: PackageManifest = packages.into();
2214
2215    manifest.version_ranges[0] = (5, 3);
2216
2217    let result = manifest.validate();
2218    assert!(result.is_err());
2219    assert!(result.unwrap_err().contains("Invalid version range"));
2220  }
2221
2222  #[test]
2223  fn test_validate_version_range_out_of_bounds() {
2224    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2225    let packages = vec![pkg];
2226    let mut manifest: PackageManifest = packages.into();
2227
2228    manifest.version_ranges[0] = (0, 999);
2229
2230    let result = manifest.validate();
2231    assert!(result.is_err());
2232    let err_msg = result.unwrap_err();
2233    assert!(err_msg.contains("only"));
2234    assert!(err_msg.contains("versions exist"));
2235  }
2236
2237  #[test]
2238  fn test_validate_mismatched_version_arrays() {
2239    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2240    let packages = vec![pkg];
2241    let mut manifest: PackageManifest = packages.into();
2242
2243    manifest.versions.download_urls.pop();
2244
2245    let result = manifest.validate();
2246    assert!(result.is_err());
2247    assert!(
2248      result
2249        .unwrap_err()
2250        .contains("versions.download_urls length")
2251    );
2252  }
2253
2254  #[test]
2255  fn test_validate_empty_manifest() {
2256    let manifest = PackageManifest {
2257      names: vec![],
2258      full_names: vec![],
2259      owners: vec![],
2260      package_urls: vec![],
2261      dates_created: vec![],
2262      dates_updated: vec![],
2263      uuid4s: vec![],
2264      rating_scores: vec![],
2265      is_pinned: vec![],
2266      is_deprecated: vec![],
2267      has_nsfw_content: vec![],
2268      categories: vec![],
2269      version_ranges: vec![],
2270      versions: VersionManifest {
2271        package_indices: vec![],
2272        version_numbers: vec![],
2273        download_urls: vec![],
2274        dependencies: vec![],
2275        dates_created: vec![],
2276        descriptions: vec![],
2277        icons: vec![],
2278        downloads: vec![],
2279        website_urls: vec![],
2280        is_active: vec![],
2281        uuid4s: vec![],
2282        file_sizes: vec![],
2283      },
2284    };
2285
2286    assert!(manifest.validate().is_ok());
2287    assert!(manifest.is_empty());
2288  }
2289
2290  #[test]
2291  fn test_validate_mismatched_package_urls_length() {
2292    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2293    let packages = vec![pkg];
2294    let mut manifest: PackageManifest = packages.into();
2295
2296    manifest.package_urls.pop();
2297
2298    let result = manifest.validate();
2299    assert!(result.is_err());
2300    assert!(result.unwrap_err().contains("package_urls length"));
2301  }
2302
2303  #[test]
2304  fn test_validate_mismatched_dates_created_length() {
2305    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2306    let packages = vec![pkg];
2307    let mut manifest: PackageManifest = packages.into();
2308
2309    manifest.dates_created.pop();
2310
2311    let result = manifest.validate();
2312    assert!(result.is_err());
2313    assert!(result.unwrap_err().contains("dates_created length"));
2314  }
2315
2316  #[test]
2317  fn test_validate_mismatched_dates_updated_length() {
2318    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2319    let packages = vec![pkg];
2320    let mut manifest: PackageManifest = packages.into();
2321
2322    manifest.dates_updated.pop();
2323
2324    let result = manifest.validate();
2325    assert!(result.is_err());
2326    assert!(result.unwrap_err().contains("dates_updated length"));
2327  }
2328
2329  #[test]
2330  fn test_validate_mismatched_uuid4s_length() {
2331    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2332    let packages = vec![pkg];
2333    let mut manifest: PackageManifest = packages.into();
2334
2335    manifest.uuid4s.pop();
2336
2337    let result = manifest.validate();
2338    assert!(result.is_err());
2339    assert!(result.unwrap_err().contains("uuid4s length"));
2340  }
2341
2342  #[test]
2343  fn test_validate_mismatched_rating_scores_length() {
2344    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2345    let packages = vec![pkg];
2346    let mut manifest: PackageManifest = packages.into();
2347
2348    manifest.rating_scores.pop();
2349
2350    let result = manifest.validate();
2351    assert!(result.is_err());
2352    assert!(result.unwrap_err().contains("rating_scores length"));
2353  }
2354
2355  #[test]
2356  fn test_validate_mismatched_is_pinned_length() {
2357    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2358    let packages = vec![pkg];
2359    let mut manifest: PackageManifest = packages.into();
2360
2361    manifest.is_pinned.pop();
2362
2363    let result = manifest.validate();
2364    assert!(result.is_err());
2365    assert!(result.unwrap_err().contains("is_pinned length"));
2366  }
2367
2368  #[test]
2369  fn test_validate_mismatched_is_deprecated_length() {
2370    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2371    let packages = vec![pkg];
2372    let mut manifest: PackageManifest = packages.into();
2373
2374    manifest.is_deprecated.pop();
2375
2376    let result = manifest.validate();
2377    assert!(result.is_err());
2378    assert!(result.unwrap_err().contains("is_deprecated length"));
2379  }
2380
2381  #[test]
2382  fn test_validate_mismatched_has_nsfw_content_length() {
2383    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2384    let packages = vec![pkg];
2385    let mut manifest: PackageManifest = packages.into();
2386
2387    manifest.has_nsfw_content.pop();
2388
2389    let result = manifest.validate();
2390    assert!(result.is_err());
2391    assert!(result.unwrap_err().contains("has_nsfw_content length"));
2392  }
2393
2394  #[test]
2395  fn test_validate_mismatched_categories_length() {
2396    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2397    let packages = vec![pkg];
2398    let mut manifest: PackageManifest = packages.into();
2399
2400    manifest.categories.pop();
2401
2402    let result = manifest.validate();
2403    assert!(result.is_err());
2404    assert!(result.unwrap_err().contains("categories length"));
2405  }
2406
2407  #[test]
2408  fn test_validate_mismatched_version_ranges_length() {
2409    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2410    let packages = vec![pkg];
2411    let mut manifest: PackageManifest = packages.into();
2412
2413    manifest.version_ranges.pop();
2414
2415    let result = manifest.validate();
2416    assert!(result.is_err());
2417    assert!(result.unwrap_err().contains("version_ranges length"));
2418  }
2419
2420  #[test]
2421  fn test_validate_mismatched_version_package_indices_length() {
2422    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2423    let packages = vec![pkg];
2424    let mut manifest: PackageManifest = packages.into();
2425
2426    manifest.versions.package_indices.pop();
2427
2428    let result = manifest.validate();
2429    assert!(result.is_err());
2430    assert!(
2431      result
2432        .unwrap_err()
2433        .contains("versions.package_indices length")
2434    );
2435  }
2436
2437  #[test]
2438  fn test_validate_mismatched_version_dependencies_length() {
2439    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2440    let packages = vec![pkg];
2441    let mut manifest: PackageManifest = packages.into();
2442
2443    manifest.versions.dependencies.pop();
2444
2445    let result = manifest.validate();
2446    assert!(result.is_err());
2447    assert!(result.unwrap_err().contains("versions.dependencies length"));
2448  }
2449
2450  #[test]
2451  fn test_validate_mismatched_version_dates_created_length() {
2452    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2453    let packages = vec![pkg];
2454    let mut manifest: PackageManifest = packages.into();
2455
2456    manifest.versions.dates_created.pop();
2457
2458    let result = manifest.validate();
2459    assert!(result.is_err());
2460    assert!(
2461      result
2462        .unwrap_err()
2463        .contains("versions.dates_created length")
2464    );
2465  }
2466
2467  #[test]
2468  fn test_validate_mismatched_version_descriptions_length() {
2469    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2470    let packages = vec![pkg];
2471    let mut manifest: PackageManifest = packages.into();
2472
2473    manifest.versions.descriptions.pop();
2474
2475    let result = manifest.validate();
2476    assert!(result.is_err());
2477    assert!(result.unwrap_err().contains("versions.descriptions length"));
2478  }
2479
2480  #[test]
2481  fn test_validate_mismatched_version_icons_length() {
2482    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2483    let packages = vec![pkg];
2484    let mut manifest: PackageManifest = packages.into();
2485
2486    manifest.versions.icons.pop();
2487
2488    let result = manifest.validate();
2489    assert!(result.is_err());
2490    assert!(result.unwrap_err().contains("versions.icons length"));
2491  }
2492
2493  #[test]
2494  fn test_validate_mismatched_version_downloads_length() {
2495    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2496    let packages = vec![pkg];
2497    let mut manifest: PackageManifest = packages.into();
2498
2499    manifest.versions.downloads.pop();
2500
2501    let result = manifest.validate();
2502    assert!(result.is_err());
2503    assert!(result.unwrap_err().contains("versions.downloads length"));
2504  }
2505
2506  #[test]
2507  fn test_validate_mismatched_version_website_urls_length() {
2508    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2509    let packages = vec![pkg];
2510    let mut manifest: PackageManifest = packages.into();
2511
2512    manifest.versions.website_urls.pop();
2513
2514    let result = manifest.validate();
2515    assert!(result.is_err());
2516    assert!(result.unwrap_err().contains("versions.website_urls length"));
2517  }
2518
2519  #[test]
2520  fn test_validate_mismatched_version_is_active_length() {
2521    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2522    let packages = vec![pkg];
2523    let mut manifest: PackageManifest = packages.into();
2524
2525    manifest.versions.is_active.pop();
2526
2527    let result = manifest.validate();
2528    assert!(result.is_err());
2529    assert!(result.unwrap_err().contains("versions.is_active length"));
2530  }
2531
2532  #[test]
2533  fn test_validate_mismatched_version_uuid4s_length() {
2534    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2535    let packages = vec![pkg];
2536    let mut manifest: PackageManifest = packages.into();
2537
2538    manifest.versions.uuid4s.pop();
2539
2540    let result = manifest.validate();
2541    assert!(result.is_err());
2542    assert!(result.unwrap_err().contains("versions.uuid4s length"));
2543  }
2544
2545  #[test]
2546  fn test_validate_mismatched_version_file_sizes_length() {
2547    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2548    let packages = vec![pkg];
2549    let mut manifest: PackageManifest = packages.into();
2550
2551    manifest.versions.file_sizes.pop();
2552
2553    let result = manifest.validate();
2554    assert!(result.is_err());
2555    assert!(result.unwrap_err().contains("versions.file_sizes length"));
2556  }
2557
2558  #[test]
2559  fn test_get_latest_version_at_empty_range() {
2560    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2561    let packages = vec![pkg];
2562    let mut manifest: PackageManifest = packages.into();
2563
2564    manifest.version_ranges[0] = (0, 0);
2565
2566    let result = manifest.get_latest_version_at(0);
2567    assert!(result.is_none());
2568  }
2569
2570  #[test]
2571  fn test_dependency_graph_resolve_with_missing_dependency() {
2572    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2573    let pkg2 = create_test_package_with_dependencies(
2574      "ModB",
2575      "Owner2",
2576      "2.0.0",
2577      vec!["Owner3-NonExistent".to_string()],
2578    );
2579
2580    let packages = vec![pkg1, pkg2];
2581    let manifest: PackageManifest = packages.into();
2582
2583    let dg = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
2584    let result = dg.resolve(&manifest);
2585
2586    assert_eq!(result.len(), 1);
2587    assert!(result.contains_key("Owner2-ModB-2.0.0.zip"));
2588  }
2589
2590  #[test]
2591  fn test_dependency_graph_resolve_with_version_suffix() {
2592    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2593
2594    let packages = vec![pkg1];
2595    let manifest: PackageManifest = packages.into();
2596
2597    let dg = DependencyGraph::new(vec!["Owner1-ModA-1.0.0".to_string()]);
2598    let result = dg.resolve(&manifest);
2599
2600    assert_eq!(result.len(), 1);
2601    assert!(result.contains_key("Owner1-ModA-1.0.0.zip"));
2602  }
2603
2604  #[test]
2605  fn test_interned_manifest_basic_operations() {
2606    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2607    let packages = vec![pkg];
2608    let interned: InternedPackageManifest = packages.into();
2609
2610    assert_eq!(interned.len(), 1);
2611
2612    assert!(!interned.is_empty());
2613
2614    assert_eq!(interned.resolve_name_at(0), Some("TestMod".to_string()));
2615
2616    assert_eq!(
2617      interned.resolve_full_name_at(0),
2618      Some("TestOwner-TestMod".to_string())
2619    );
2620
2621    assert_eq!(interned.resolve_owner_at(0), Some("TestOwner".to_string()));
2622  }
2623
2624  #[test]
2625  fn test_interned_manifest_get_package_by_full_name() {
2626    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2627    let packages = vec![pkg];
2628    let interned: InternedPackageManifest = packages.into();
2629
2630    let found = interned.get_package_by_full_name("TestOwner-TestMod");
2631
2632    assert!(found.is_some());
2633
2634    let package = found.unwrap();
2635
2636    assert_eq!(package.name, Some("TestMod".to_string()));
2637
2638    assert_eq!(package.owner, Some("TestOwner".to_string()));
2639
2640    assert_eq!(package.versions.len(), 1);
2641  }
2642
2643  #[test]
2644  fn test_interned_manifest_find_index_by_full_name() {
2645    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2646    let pkg2 = create_test_package("ModB", "Owner2", "2.0.0");
2647    let packages = vec![pkg1, pkg2];
2648    let interned: InternedPackageManifest = packages.into();
2649
2650    assert_eq!(interned.find_index_by_full_name("Owner1-ModA"), Some(0));
2651
2652    assert_eq!(interned.find_index_by_full_name("Owner2-ModB"), Some(1));
2653
2654    assert_eq!(interned.find_index_by_full_name("NonExistent"), None);
2655  }
2656
2657  #[test]
2658  fn test_interned_manifest_get_package_at() {
2659    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2660    let packages = vec![pkg];
2661    let interned: InternedPackageManifest = packages.into();
2662
2663    let package = interned.get_package_at(0);
2664
2665    assert_eq!(package.name, Some("TestMod".to_string()));
2666
2667    assert_eq!(package.full_name, Some("TestOwner-TestMod".to_string()));
2668
2669    assert_eq!(package.owner, Some("TestOwner".to_string()));
2670
2671    assert_eq!(package.versions.len(), 1);
2672
2673    assert_eq!(
2674      package.versions[0].version_number,
2675      Some("1.0.0".to_string())
2676    );
2677  }
2678
2679  #[test]
2680  fn test_interned_manifest_get_latest_version_at() {
2681    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2682
2683    let older_version = Version {
2684      name: Some("TestMod".to_string()),
2685      full_name: Some("TestOwner-TestMod".to_string()),
2686      description: Some("Older version".to_string()),
2687      icon: Some("icon.png".to_string()),
2688      version_number: Some("0.9.0".to_string()),
2689      dependencies: vec![],
2690      download_url: Some("https://example.com/TestMod/download-old".to_string()),
2691      downloads: Some(50),
2692      date_created: OffsetDateTime::now_utc().saturating_sub(time::Duration::days(30)),
2693      website_url: Some("https://example.com".to_string()),
2694      is_active: Some(true),
2695      uuid4: Some("old-version-uuid".to_string()),
2696      file_size: Some(512),
2697    };
2698    pkg.versions.insert(0, older_version);
2699
2700    let packages = vec![pkg];
2701    let interned: InternedPackageManifest = packages.into();
2702
2703    let latest_idx = interned.get_latest_version_at(0);
2704
2705    assert!(latest_idx.is_some());
2706
2707    let version_idx = latest_idx.unwrap();
2708    let version_number = interned.versions.version_numbers[version_idx]
2709      .map(|key| interned.interner.resolve(&key).to_string());
2710
2711    assert_eq!(version_number, Some("1.0.0".to_string()));
2712  }
2713
2714  #[test]
2715  fn test_interned_manifest_get_latest_version_at_empty_range() {
2716    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2717    let packages = vec![pkg];
2718    let mut interned: InternedPackageManifest = packages.into();
2719
2720    interned.version_ranges[0] = (0, 0);
2721
2722    let latest = interned.get_latest_version_at(0);
2723
2724    assert!(latest.is_none());
2725  }
2726
2727  #[test]
2728  fn test_interned_manifest_build_name_index() {
2729    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2730    let pkg2 = create_test_package("ModB", "Owner2", "2.0.0");
2731    let packages = vec![pkg1, pkg2];
2732    let interned: InternedPackageManifest = packages.into();
2733
2734    let index = interned.build_name_index();
2735
2736    assert_eq!(index.len(), 2);
2737
2738    assert_eq!(index.get("Owner1-ModA"), Some(&0));
2739
2740    assert_eq!(index.get("Owner2-ModB"), Some(&1));
2741  }
2742
2743  #[test]
2744  fn test_interned_manifest_validate_valid() {
2745    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2746    let packages = vec![pkg];
2747    let interned: InternedPackageManifest = packages.into();
2748
2749    let result = interned.validate();
2750
2751    assert!(result.is_ok());
2752  }
2753
2754  #[test]
2755  fn test_interned_manifest_validate_mismatched_full_names_length() {
2756    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2757    let packages = vec![pkg];
2758    let mut interned: InternedPackageManifest = packages.into();
2759
2760    interned.full_names.pop();
2761
2762    let result = interned.validate();
2763
2764    assert!(result.is_err());
2765
2766    assert!(result.unwrap_err().contains("full_names length"));
2767  }
2768
2769  #[test]
2770  fn test_interned_manifest_validate_mismatched_owners_length() {
2771    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2772    let packages = vec![pkg];
2773    let mut interned: InternedPackageManifest = packages.into();
2774
2775    interned.owners.pop();
2776
2777    let result = interned.validate();
2778
2779    assert!(result.is_err());
2780
2781    assert!(result.unwrap_err().contains("owners length"));
2782  }
2783
2784  #[test]
2785  fn test_interned_manifest_validate_mismatched_package_urls_length() {
2786    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2787    let packages = vec![pkg];
2788    let mut interned: InternedPackageManifest = packages.into();
2789
2790    interned.package_urls.pop();
2791
2792    let result = interned.validate();
2793
2794    assert!(result.is_err());
2795
2796    assert!(result.unwrap_err().contains("package_urls length"));
2797  }
2798
2799  #[test]
2800  fn test_interned_manifest_validate_mismatched_dates_created_length() {
2801    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2802    let packages = vec![pkg];
2803    let mut interned: InternedPackageManifest = packages.into();
2804
2805    interned.dates_created.pop();
2806
2807    let result = interned.validate();
2808
2809    assert!(result.is_err());
2810
2811    assert!(result.unwrap_err().contains("dates_created length"));
2812  }
2813
2814  #[test]
2815  fn test_interned_manifest_validate_mismatched_dates_updated_length() {
2816    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2817    let packages = vec![pkg];
2818    let mut interned: InternedPackageManifest = packages.into();
2819
2820    interned.dates_updated.pop();
2821
2822    let result = interned.validate();
2823
2824    assert!(result.is_err());
2825
2826    assert!(result.unwrap_err().contains("dates_updated length"));
2827  }
2828
2829  #[test]
2830  fn test_interned_manifest_validate_mismatched_uuid4s_length() {
2831    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2832    let packages = vec![pkg];
2833    let mut interned: InternedPackageManifest = packages.into();
2834
2835    interned.uuid4s.pop();
2836
2837    let result = interned.validate();
2838
2839    assert!(result.is_err());
2840
2841    assert!(result.unwrap_err().contains("uuid4s length"));
2842  }
2843
2844  #[test]
2845  fn test_interned_manifest_validate_mismatched_rating_scores_length() {
2846    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2847    let packages = vec![pkg];
2848    let mut interned: InternedPackageManifest = packages.into();
2849
2850    interned.rating_scores.pop();
2851
2852    let result = interned.validate();
2853
2854    assert!(result.is_err());
2855
2856    assert!(result.unwrap_err().contains("rating_scores length"));
2857  }
2858
2859  #[test]
2860  fn test_interned_manifest_validate_mismatched_is_pinned_length() {
2861    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2862    let packages = vec![pkg];
2863    let mut interned: InternedPackageManifest = packages.into();
2864
2865    interned.is_pinned.pop();
2866
2867    let result = interned.validate();
2868
2869    assert!(result.is_err());
2870
2871    assert!(result.unwrap_err().contains("is_pinned length"));
2872  }
2873
2874  #[test]
2875  fn test_interned_manifest_validate_mismatched_is_deprecated_length() {
2876    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2877    let packages = vec![pkg];
2878    let mut interned: InternedPackageManifest = packages.into();
2879
2880    interned.is_deprecated.pop();
2881
2882    let result = interned.validate();
2883
2884    assert!(result.is_err());
2885
2886    assert!(result.unwrap_err().contains("is_deprecated length"));
2887  }
2888
2889  #[test]
2890  fn test_interned_manifest_validate_mismatched_has_nsfw_content_length() {
2891    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2892    let packages = vec![pkg];
2893    let mut interned: InternedPackageManifest = packages.into();
2894
2895    interned.has_nsfw_content.pop();
2896
2897    let result = interned.validate();
2898
2899    assert!(result.is_err());
2900
2901    assert!(result.unwrap_err().contains("has_nsfw_content length"));
2902  }
2903
2904  #[test]
2905  fn test_interned_manifest_validate_mismatched_categories_length() {
2906    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2907    let packages = vec![pkg];
2908    let mut interned: InternedPackageManifest = packages.into();
2909
2910    interned.categories.pop();
2911
2912    let result = interned.validate();
2913
2914    assert!(result.is_err());
2915
2916    assert!(result.unwrap_err().contains("categories length"));
2917  }
2918
2919  #[test]
2920  fn test_interned_manifest_validate_mismatched_version_ranges_length() {
2921    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2922    let packages = vec![pkg];
2923    let mut interned: InternedPackageManifest = packages.into();
2924
2925    interned.version_ranges.pop();
2926
2927    let result = interned.validate();
2928
2929    assert!(result.is_err());
2930
2931    assert!(result.unwrap_err().contains("version_ranges length"));
2932  }
2933
2934  #[test]
2935  fn test_interned_manifest_validate_mismatched_version_numbers_length() {
2936    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2937    let packages = vec![pkg];
2938    let mut interned: InternedPackageManifest = packages.into();
2939
2940    interned.versions.version_numbers.pop();
2941
2942    let result = interned.validate();
2943
2944    assert!(result.is_err());
2945
2946    assert!(result.unwrap_err().contains("version_numbers length"));
2947  }
2948
2949  #[test]
2950  fn test_interned_manifest_validate_mismatched_version_download_urls_length() {
2951    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2952    let packages = vec![pkg];
2953    let mut interned: InternedPackageManifest = packages.into();
2954
2955    interned.versions.download_urls.pop();
2956
2957    let result = interned.validate();
2958
2959    assert!(result.is_err());
2960
2961    assert!(result.unwrap_err().contains("download_urls length"));
2962  }
2963
2964  #[test]
2965  fn test_interned_manifest_validate_mismatched_version_dependencies_length() {
2966    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2967    let packages = vec![pkg];
2968    let mut interned: InternedPackageManifest = packages.into();
2969
2970    interned.versions.dependencies.pop();
2971
2972    let result = interned.validate();
2973
2974    assert!(result.is_err());
2975
2976    assert!(result.unwrap_err().contains("dependencies length"));
2977  }
2978
2979  #[test]
2980  fn test_interned_manifest_validate_mismatched_version_dates_created_length() {
2981    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2982    let packages = vec![pkg];
2983    let mut interned: InternedPackageManifest = packages.into();
2984
2985    interned.versions.dates_created.pop();
2986
2987    let result = interned.validate();
2988
2989    assert!(result.is_err());
2990
2991    assert!(result.unwrap_err().contains("dates_created length"));
2992  }
2993
2994  #[test]
2995  fn test_interned_manifest_validate_mismatched_version_descriptions_length() {
2996    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2997    let packages = vec![pkg];
2998    let mut interned: InternedPackageManifest = packages.into();
2999
3000    interned.versions.descriptions.pop();
3001
3002    let result = interned.validate();
3003
3004    assert!(result.is_err());
3005
3006    assert!(result.unwrap_err().contains("descriptions length"));
3007  }
3008
3009  #[test]
3010  fn test_interned_manifest_validate_mismatched_version_icons_length() {
3011    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3012    let packages = vec![pkg];
3013    let mut interned: InternedPackageManifest = packages.into();
3014
3015    interned.versions.icons.pop();
3016
3017    let result = interned.validate();
3018
3019    assert!(result.is_err());
3020
3021    assert!(result.unwrap_err().contains("icons length"));
3022  }
3023
3024  #[test]
3025  fn test_interned_manifest_validate_mismatched_version_downloads_length() {
3026    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3027    let packages = vec![pkg];
3028    let mut interned: InternedPackageManifest = packages.into();
3029
3030    interned.versions.downloads.pop();
3031
3032    let result = interned.validate();
3033
3034    assert!(result.is_err());
3035
3036    assert!(result.unwrap_err().contains("downloads length"));
3037  }
3038
3039  #[test]
3040  fn test_interned_manifest_validate_mismatched_version_website_urls_length() {
3041    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3042    let packages = vec![pkg];
3043    let mut interned: InternedPackageManifest = packages.into();
3044
3045    interned.versions.website_urls.pop();
3046
3047    let result = interned.validate();
3048
3049    assert!(result.is_err());
3050
3051    assert!(result.unwrap_err().contains("website_urls length"));
3052  }
3053
3054  #[test]
3055  fn test_interned_manifest_validate_mismatched_version_is_active_length() {
3056    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3057    let packages = vec![pkg];
3058    let mut interned: InternedPackageManifest = packages.into();
3059
3060    interned.versions.is_active.pop();
3061
3062    let result = interned.validate();
3063
3064    assert!(result.is_err());
3065
3066    assert!(result.unwrap_err().contains("is_active length"));
3067  }
3068
3069  #[test]
3070  fn test_interned_manifest_validate_mismatched_version_uuid4s_length() {
3071    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3072    let packages = vec![pkg];
3073    let mut interned: InternedPackageManifest = packages.into();
3074
3075    interned.versions.uuid4s.pop();
3076
3077    let result = interned.validate();
3078
3079    assert!(result.is_err());
3080
3081    assert!(result.unwrap_err().contains("uuid4s length"));
3082  }
3083
3084  #[test]
3085  fn test_interned_manifest_validate_mismatched_version_file_sizes_length() {
3086    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3087    let packages = vec![pkg];
3088    let mut interned: InternedPackageManifest = packages.into();
3089
3090    interned.versions.file_sizes.pop();
3091
3092    let result = interned.validate();
3093
3094    assert!(result.is_err());
3095
3096    assert!(result.unwrap_err().contains("file_sizes length"));
3097  }
3098
3099  #[test]
3100  fn test_interned_manifest_validate_invalid_version_range() {
3101    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3102    let packages = vec![pkg];
3103    let mut interned: InternedPackageManifest = packages.into();
3104
3105    interned.version_ranges[0] = (5, 3);
3106
3107    let result = interned.validate();
3108
3109    assert!(result.is_err());
3110
3111    assert!(result.unwrap_err().contains("Invalid version range"));
3112  }
3113
3114  #[test]
3115  fn test_interned_manifest_validate_version_range_out_of_bounds() {
3116    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3117    let packages = vec![pkg];
3118    let mut interned: InternedPackageManifest = packages.into();
3119
3120    interned.version_ranges[0] = (0, 999);
3121
3122    let result = interned.validate();
3123
3124    assert!(result.is_err());
3125
3126    assert!(result.unwrap_err().contains("ends at"));
3127  }
3128
3129  #[test]
3130  fn test_interned_manifest_serialization_round_trip() {
3131    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3132    let packages = vec![pkg.clone()];
3133    let interned: InternedPackageManifest = packages.into();
3134
3135    let serializable: SerializableInternedManifest = (&interned).into();
3136
3137    let binary = bincode::serialize(&serializable).unwrap();
3138
3139    let deserialized: SerializableInternedManifest = bincode::deserialize(&binary).unwrap();
3140
3141    let recovered: InternedPackageManifest = deserialized.into();
3142
3143    assert_eq!(recovered.len(), 1);
3144
3145    assert_eq!(recovered.resolve_name_at(0), Some("TestMod".to_string()));
3146
3147    assert_eq!(
3148      recovered.resolve_full_name_at(0),
3149      Some("TestOwner-TestMod".to_string())
3150    );
3151
3152    let recovered_pkg = recovered.get_package_at(0);
3153
3154    assert_eq!(recovered_pkg.name, pkg.name);
3155
3156    assert_eq!(recovered_pkg.full_name, pkg.full_name);
3157
3158    assert_eq!(recovered_pkg.versions.len(), pkg.versions.len());
3159  }
3160
3161  #[test]
3162  fn test_interned_manifest_conversion_from_package_manifest() {
3163    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3164    let packages = vec![pkg];
3165    let v2_manifest: PackageManifest = packages.into();
3166
3167    let interned: InternedPackageManifest = v2_manifest.clone().into();
3168
3169    assert_eq!(interned.len(), v2_manifest.len());
3170
3171    assert_eq!(interned.resolve_name_at(0), v2_manifest.names[0].clone());
3172
3173    assert_eq!(
3174      interned.resolve_full_name_at(0),
3175      v2_manifest.full_names[0].clone()
3176    );
3177  }
3178
3179  #[test]
3180  fn test_interned_manifest_dependency_graph_resolve() {
3181    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
3182    let pkg2 = create_test_package_with_dependencies(
3183      "ModB",
3184      "Owner2",
3185      "2.0.0",
3186      vec!["Owner3-ModC".to_string()],
3187    );
3188    let pkg3 = create_test_package_with_dependencies(
3189      "ModC",
3190      "Owner3",
3191      "1.5.0",
3192      vec!["Owner4-ModD".to_string()],
3193    );
3194    let pkg4 = create_test_package("ModD", "Owner4", "0.9.0");
3195
3196    let packages = vec![pkg1, pkg2, pkg3, pkg4];
3197    let interned: InternedPackageManifest = packages.into();
3198
3199    let dg1 = DependencyGraph::new(vec!["Owner1-ModA".to_string()]);
3200    let result1 = dg1.resolve_interned(&interned);
3201
3202    assert_eq!(result1.len(), 1);
3203
3204    assert!(result1.contains_key("Owner1-ModA-1.0.0.zip"));
3205
3206    let dg2 = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
3207    let result2 = dg2.resolve_interned(&interned);
3208
3209    assert_eq!(result2.len(), 3);
3210
3211    assert!(result2.contains_key("Owner2-ModB-2.0.0.zip"));
3212
3213    assert!(result2.contains_key("Owner3-ModC-1.5.0.zip"));
3214
3215    assert!(result2.contains_key("Owner4-ModD-0.9.0.zip"));
3216  }
3217
3218  #[test]
3219  fn test_interned_manifest_dependency_graph_resolve_with_missing_dependency() {
3220    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
3221    let pkg2 = create_test_package_with_dependencies(
3222      "ModB",
3223      "Owner2",
3224      "2.0.0",
3225      vec!["Owner3-NonExistent".to_string()],
3226    );
3227
3228    let packages = vec![pkg1, pkg2];
3229    let interned: InternedPackageManifest = packages.into();
3230
3231    let dg = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
3232    let result = dg.resolve_interned(&interned);
3233
3234    assert_eq!(result.len(), 1);
3235
3236    assert!(result.contains_key("Owner2-ModB-2.0.0.zip"));
3237  }
3238
3239  #[test]
3240  fn test_interned_manifest_dependency_graph_resolve_with_version_suffix() {
3241    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
3242
3243    let packages = vec![pkg1];
3244    let interned: InternedPackageManifest = packages.into();
3245
3246    let dg = DependencyGraph::new(vec!["Owner1-ModA-1.0.0".to_string()]);
3247    let result = dg.resolve_interned(&interned);
3248
3249    assert_eq!(result.len(), 1);
3250
3251    assert!(result.contains_key("Owner1-ModA-1.0.0.zip"));
3252  }
3253}