Skip to main content

releasaurus_core/packages/
resolved_hash.rs

1//! HashMap-based collection for managing resolved packages.
2//!
3//! Provides efficient lookup by package name with proper error
4//! handling for missing packages and duplicate detection.
5
6use std::collections::HashMap;
7
8use crate::{
9    packages::resolved::ResolvedPackage,
10    result::{ReleasaurusError, Result},
11};
12
13pub type PackageName = String;
14
15/// Collection of resolved packages indexed by name.
16///
17/// Ensures uniqueness of package names and provides efficient
18/// lookup operations.
19#[derive(Debug)]
20pub struct ResolvedPackageHash {
21    hash: HashMap<PackageName, ResolvedPackage>,
22}
23
24impl ResolvedPackageHash {
25    /// Creates a new hash from a vector of resolved packages.
26    ///
27    /// # Errors
28    ///
29    /// Returns an error if duplicate package names are detected.
30    pub fn new(package_configs: Vec<ResolvedPackage>) -> Result<Self> {
31        let mut hash = HashMap::with_capacity(package_configs.len());
32
33        for pkg in package_configs {
34            let name = pkg.name.clone();
35            if hash.insert(name.clone(), pkg).is_some() {
36                return Err(ReleasaurusError::invalid_config(format!(
37                    "Duplicate package name found: '{}'",
38                    name
39                )));
40            }
41        }
42
43        Ok(Self { hash })
44    }
45
46    /// Returns a reference to the underlying HashMap.
47    ///
48    /// Useful for iterating over all packages.
49    pub fn hash(&self) -> &HashMap<String, ResolvedPackage> {
50        &self.hash
51    }
52
53    /// Gets a package by name.
54    ///
55    /// # Errors
56    ///
57    /// Returns an error if the package name is not found.
58    pub fn get(&self, name: &str) -> Result<&ResolvedPackage> {
59        self.hash.get(name).ok_or_else(|| {
60            ReleasaurusError::invalid_config(format!(
61                "Package not found: '{}'",
62                name
63            ))
64        })
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use crate::{
71        analyzer::config::AnalyzerConfig, config::release_type::ReleaseType,
72    };
73
74    use super::*;
75    use std::path::PathBuf;
76
77    fn create_test_package(name: &str) -> ResolvedPackage {
78        ResolvedPackage {
79            name: name.to_string(),
80            normalized_workspace_root: PathBuf::from("."),
81            normalized_full_path: PathBuf::from("."),
82            release_type: ReleaseType::default(),
83            tag_prefix: "v".to_string(),
84            sub_packages: vec![],
85            prerelease: None,
86            auto_start_next: false,
87            normalized_additional_paths: vec![],
88            compiled_additional_manifests: vec![],
89            analyzer_config: AnalyzerConfig::default(),
90        }
91    }
92
93    #[test]
94    fn creates_hash_from_packages() {
95        let packages =
96            vec![create_test_package("pkg1"), create_test_package("pkg2")];
97
98        let hash = ResolvedPackageHash::new(packages).unwrap();
99        assert_eq!(hash.hash().len(), 2);
100    }
101
102    #[test]
103    fn rejects_duplicate_names() {
104        let packages =
105            vec![create_test_package("pkg1"), create_test_package("pkg1")];
106
107        let result = ResolvedPackageHash::new(packages);
108        assert!(result.is_err());
109    }
110
111    #[test]
112    fn gets_package_by_name() {
113        let packages = vec![create_test_package("test-pkg")];
114        let hash = ResolvedPackageHash::new(packages).unwrap();
115
116        let pkg = hash.get("test-pkg").unwrap();
117        assert_eq!(pkg.name, "test-pkg");
118    }
119
120    #[test]
121    fn returns_error_for_missing_package() {
122        let packages = vec![create_test_package("pkg1")];
123        let hash = ResolvedPackageHash::new(packages).unwrap();
124
125        let result = hash.get("pkg2");
126        assert!(result.is_err());
127    }
128}