Skip to main content

actr_cli/core/components/
fingerprint_validator.rs

1//! Default FingerprintValidator implementation
2
3use anyhow::Result;
4use async_trait::async_trait;
5use sha2::{Digest, Sha256};
6use std::io::Read;
7use std::path::Path;
8use walkdir::WalkDir;
9
10use super::{Fingerprint, FingerprintValidator, ResolvedDependency, ServiceInfo};
11
12/// Default fingerprint validator
13pub struct DefaultFingerprintValidator;
14
15impl DefaultFingerprintValidator {
16    pub fn new() -> Self {
17        Self
18    }
19
20    /// Compute SHA256 of a file's content
21    fn hash_file(path: &Path) -> Result<Vec<u8>> {
22        let mut hasher = Sha256::new();
23        let mut file = std::fs::File::open(path)?;
24        let mut buffer = [0u8; 8192];
25
26        loop {
27            let count = file.read(&mut buffer)?;
28            if count == 0 {
29                break;
30            }
31            hasher.update(&buffer[..count]);
32        }
33
34        Ok(hasher.finalize().to_vec())
35    }
36}
37
38impl Default for DefaultFingerprintValidator {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44#[async_trait]
45impl FingerprintValidator for DefaultFingerprintValidator {
46    async fn compute_service_fingerprint(&self, service: &ServiceInfo) -> Result<Fingerprint> {
47        Ok(Fingerprint {
48            algorithm: "sha256".to_string(),
49            value: service.fingerprint.clone(),
50        })
51    }
52
53    async fn verify_fingerprint(
54        &self,
55        expected: &Fingerprint,
56        actual: &Fingerprint,
57    ) -> Result<bool> {
58        Ok(expected.algorithm == actual.algorithm && expected.value == actual.value)
59    }
60
61    async fn compute_project_fingerprint(&self, project_path: &Path) -> Result<Fingerprint> {
62        let mut hasher = Sha256::new();
63        let mut proto_files: Vec<_> = WalkDir::new(project_path)
64            .into_iter()
65            .filter_map(|e| e.ok())
66            .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("proto"))
67            .collect();
68
69        // Sort files to ensure deterministic hash
70        proto_files.sort_by(|a, b| a.path().cmp(b.path()));
71
72        for entry in proto_files {
73            let file_hash = Self::hash_file(entry.path())?;
74            hasher.update(&file_hash);
75        }
76
77        Ok(Fingerprint {
78            algorithm: "sha256".to_string(),
79            value: hex::encode(hasher.finalize()),
80        })
81    }
82
83    async fn generate_lock_fingerprint(&self, deps: &[ResolvedDependency]) -> Result<Fingerprint> {
84        let mut hasher = Sha256::new();
85        let mut dep_names: Vec<_> = deps.iter().map(|d| &d.spec.name).collect();
86        dep_names.sort();
87
88        for name in dep_names {
89            hasher.update(name.as_bytes());
90            if let Some(dep) = deps.iter().find(|d| d.spec.name == *name) {
91                hasher.update(dep.fingerprint.as_bytes());
92            }
93        }
94
95        Ok(Fingerprint {
96            algorithm: "sha256".to_string(),
97            value: hex::encode(hasher.finalize()),
98        })
99    }
100}