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