Skip to main content

wapm_cli/data/lock/
lockfile.rs

1use crate::data::lock::lockfile_command::LockfileCommand;
2use crate::data::lock::lockfile_module::{
3    LockfileModule, LockfileModuleV2, LockfileModuleV3, LockfileModuleV4,
4};
5use crate::data::lock::{LOCKFILE_HEADER, LOCKFILE_NAME};
6use semver::Version;
7use std::collections::BTreeMap;
8use std::fs::File;
9use std::io;
10use std::io::Write;
11use std::path::Path;
12use thiserror::Error;
13
14pub type ModuleMapV2 = BTreeMap<String, BTreeMap<Version, BTreeMap<String, LockfileModuleV2>>>;
15pub type CommandMapV2 = BTreeMap<String, LockfileCommand>;
16
17/// The lockfile for versions 2 and below (no changes to the fields happened until version 3,
18/// so these can be a singel struct)
19#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
20pub struct LockfileV2 {
21    pub modules: ModuleMapV2, // PackageName -> VersionNumber -> ModuleName -> Module
22    pub commands: CommandMapV2, // CommandName -> Command
23}
24
25pub type ModuleMapV3 = BTreeMap<String, BTreeMap<Version, BTreeMap<String, LockfileModuleV3>>>;
26pub type CommandMapV3 = BTreeMap<String, LockfileCommand>;
27
28/// The latest Lockfile version
29#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
30pub struct LockfileV3 {
31    pub modules: ModuleMapV3, // PackageName -> VersionNumber -> ModuleName -> Module
32    pub commands: CommandMapV3, // CommandName -> Command
33}
34
35pub type ModuleMap = BTreeMap<String, BTreeMap<Version, BTreeMap<String, LockfileModuleV4>>>;
36pub type CommandMap = BTreeMap<String, LockfileCommand>;
37
38/// The latest Lockfile version
39#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
40pub struct Lockfile {
41    pub modules: ModuleMap, // PackageName -> VersionNumber -> ModuleName -> Module
42    pub commands: CommandMap, // CommandName -> Command
43}
44
45pub type LockfileV4 = Lockfile;
46pub type ModuleMapV4 = ModuleMap;
47pub type CommandMapV4 = CommandMap;
48
49impl Lockfile {
50    /// Save the lockfile to the directory.
51    pub fn save<P: AsRef<Path>>(&self, directory: P) -> anyhow::Result<()> {
52        let lockfile_string = toml::to_string(self)?;
53        let lockfile_string = format!("{}\n{}", LOCKFILE_HEADER, lockfile_string);
54        let lockfile_path = directory.as_ref().join(LOCKFILE_NAME);
55        let mut file = File::create(lockfile_path)?;
56        file.write_all(lockfile_string.as_bytes())?;
57        Ok(())
58    }
59
60    /// Looks up the prehashed cache key based on data in the Command
61    pub fn get_prehashed_cache_key_from_command(
62        &self,
63        command: &LockfileCommand,
64    ) -> Option<String> {
65        self.modules
66            .get(&command.package_name)
67            .and_then(|version_map| version_map.get(&command.package_version))
68            .and_then(|module_map| module_map.get(&command.module))
69            .and_then(|module| module.prehashed_module_key.clone())
70    }
71
72    pub fn get_command(&self, command_name: &str) -> Result<&LockfileCommand, LockfileError> {
73        self.commands
74            .get(command_name)
75            .ok_or_else(|| LockfileError::CommandNotFound(command_name.to_string()))
76    }
77
78    pub fn get_module(
79        &self,
80        package_name: &str,
81        package_version: &Version,
82        module_name: &str,
83    ) -> anyhow::Result<&LockfileModule> {
84        let version_map = self.modules.get(package_name).ok_or_else(|| {
85            LockfileError::PackageWithVersionNotFoundWhenFindingModule(
86                package_name.to_string(),
87                package_version.to_string(),
88                module_name.to_string(),
89            )
90        })?;
91        let module_map = version_map.get(package_version).ok_or_else(|| {
92            LockfileError::VersionNotFoundForPackageWhenFindingModule(
93                package_name.to_string(),
94                package_version.to_string(),
95                module_name.to_string(),
96            )
97        })?;
98        let module = module_map.get(module_name).ok_or_else(|| {
99            LockfileError::ModuleForPackageVersionNotFound(
100                package_name.to_string(),
101                package_version.to_string(),
102                module_name.to_string(),
103            )
104        })?;
105        Ok(module)
106    }
107}
108
109#[derive(Debug, Error)]
110pub enum LockfileError {
111    #[error("Command not found: {0}")]
112    CommandNotFound(String),
113    #[error("module {2} in package \"{0} {1}\" was not found")]
114    ModuleForPackageVersionNotFound(String, String, String),
115    #[error("Module \"{2}\" with package name \"{0}\" and version \"{1}\" was not found.")]
116    PackageWithVersionNotFoundWhenFindingModule(String, String, String),
117    #[error(
118        "version \"{1}\" for package \"{0}\" was not found when searching for module \"{2}\"."
119    )]
120    VersionNotFoundForPackageWhenFindingModule(String, String, String),
121    #[error("Lockfile file not found.")]
122    MissingLockfile,
123    #[error("File I/O error reading lockfile. I/O error: {0:?}")]
124    FileIoErrorReadingLockfile(io::Error),
125    #[error(
126        "Failed to parse lockfile toml. Did you modify the generated lockfile? Toml error: {0:?}"
127    )]
128    TomlParseError(toml::de::Error),
129}