pray_core/
package_spec_hash.rs1use 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}