Skip to main content

pray_core/
package_spec_hash.rs

1use super::PackageSpec;
2use crate::hashing::sha256_prefixed;
3use crate::{PrayError, PrayResult};
4use std::collections::BTreeMap;
5
6impl PackageSpec {
7    pub fn canonicalized(&self) -> Self {
8        let mut package = self.clone();
9        package.files.sort();
10        package.authors.sort();
11        package.maintainers.sort();
12        package.targets.sort();
13        package.dependencies.sort_by(|left, right| {
14            left.name
15                .cmp(&right.name)
16                .then(left.constraint.cmp(&right.constraint))
17                .then(left.optional.cmp(&right.optional))
18        });
19        package
20    }
21
22    pub fn validate_relative_paths(&self) -> PrayResult<()> {
23        use crate::paths::validate_package_relative_path;
24        use std::path::Path;
25        for file in &self.files {
26            validate_package_relative_path(Path::new(file))?;
27        }
28        for export in self.exports.values() {
29            validate_package_relative_path(Path::new(&export.path))?;
30            if let Some(default_path) = &export.default_path {
31                validate_package_relative_path(Path::new(default_path))?;
32            }
33        }
34        for skill in self.skills.values() {
35            validate_package_relative_path(Path::new(&skill.path))?;
36        }
37        for template in self.templates.values() {
38            validate_package_relative_path(Path::new(&template.path))?;
39        }
40        Ok(())
41    }
42
43    pub fn tree_hash_for_root(&self, root: &std::path::Path) -> PrayResult<String> {
44        self.validate_relative_paths()?;
45        let mut file_bytes = BTreeMap::new();
46        for file in &self.files {
47            let path = root.join(file);
48            if !path.exists() {
49                return Err(PrayError::Integrity(format!(
50                    "package file missing: {file}"
51                )));
52            }
53            if path.is_dir() {
54                return Err(PrayError::Integrity(format!(
55                    "package file is a directory: {file}"
56                )));
57            }
58            file_bytes.insert(file.clone(), std::fs::read(&path)?);
59        }
60        Self::tree_hash_from_file_bytes(&file_bytes)
61    }
62
63    pub fn tree_hash_from_file_bytes(file_bytes: &BTreeMap<String, Vec<u8>>) -> PrayResult<String> {
64        let mut entries = file_bytes
65            .iter()
66            .map(|(path, bytes)| (path.clone(), sha256_prefixed(bytes)))
67            .collect::<Vec<_>>();
68        entries.sort_by(|left, right| left.0.cmp(&right.0));
69
70        let mut serialized = String::new();
71        for (path, hash) in entries {
72            serialized.push_str("file\0regular\0");
73            serialized.push_str(&path);
74            serialized.push('\0');
75            serialized.push_str(&hash);
76            serialized.push('\n');
77        }
78        Ok(sha256_prefixed(serialized.as_bytes()))
79    }
80}