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.targets.sort();
12 package.dependencies.sort_by(|left, right| {
13 left.name
14 .cmp(&right.name)
15 .then(left.constraint.cmp(&right.constraint))
16 .then(left.optional.cmp(&right.optional))
17 });
18 package
19 }
20
21 pub fn tree_hash_for_root(&self, root: &std::path::Path) -> PrayResult<String> {
22 let mut file_bytes = BTreeMap::new();
23 for file in &self.files {
24 let path = root.join(file);
25 if !path.exists() {
26 return Err(PrayError::Integrity(format!(
27 "package file missing: {file}"
28 )));
29 }
30 if path.is_dir() {
31 return Err(PrayError::Integrity(format!(
32 "package file is a directory: {file}"
33 )));
34 }
35 file_bytes.insert(file.clone(), std::fs::read(&path)?);
36 }
37 Self::tree_hash_from_file_bytes(&file_bytes)
38 }
39
40 pub fn tree_hash_from_file_bytes(file_bytes: &BTreeMap<String, Vec<u8>>) -> PrayResult<String> {
41 let mut entries = file_bytes
42 .iter()
43 .map(|(path, bytes)| (path.clone(), sha256_prefixed(bytes)))
44 .collect::<Vec<_>>();
45 entries.sort_by(|left, right| left.0.cmp(&right.0));
46
47 let mut serialized = String::new();
48 for (path, hash) in entries {
49 serialized.push_str("file\0regular\0");
50 serialized.push_str(&path);
51 serialized.push('\0');
52 serialized.push_str(&hash);
53 serialized.push('\n');
54 }
55 Ok(sha256_prefixed(serialized.as_bytes()))
56 }
57}