conduit_cli/core/project/
verify.rs1use crate::core::error::CoreResult;
2use crate::core::io::project::ProjectFiles;
3use crate::core::paths::CorePaths;
4use crate::core::io::hash::{sha256_file, sha1_file};
5
6#[derive(Debug, Clone, Copy)]
7pub enum VerifyScope {
8 Modrinth,
9 Local,
10}
11
12#[derive(Debug, Clone, Default)]
13pub struct VerifyReport {
14 pub ok: usize,
15 pub mismatch: Vec<VerifyMismatch>,
16 pub missing: Vec<VerifyMissing>,
17}
18
19#[derive(Debug, Clone)]
20pub struct VerifyMismatch {
21 pub key: String,
22 pub filename: String,
23 pub expected: String,
24 pub actual: String,
25}
26
27#[derive(Debug, Clone)]
28pub struct VerifyMissing {
29 pub key: String,
30 pub filename: String,
31}
32
33pub fn verify_project(paths: &CorePaths, scope: VerifyScope) -> CoreResult<VerifyReport> {
34 let lock = ProjectFiles::load_lock(paths)?;
35
36 let mut report = VerifyReport::default();
37
38 for (key, m) in &lock.locked_mods {
39 let is_local = m.url == "local";
40 match scope {
41 VerifyScope::Modrinth if is_local => continue,
42 VerifyScope::Local if !is_local => continue,
43 _ => {}
44 }
45
46 let mod_path = paths.mods_dir().join(&m.filename);
47 if !mod_path.exists() {
48 report.missing.push(VerifyMissing {
49 key: key.clone(),
50 filename: m.filename.clone(),
51 });
52 continue;
53 }
54
55 let actual = if is_local {
56 sha256_file(&mod_path)?
57 } else {
58 sha1_file(&mod_path)?
59 };
60
61 if actual.eq_ignore_ascii_case(&m.hash) {
62 report.ok += 1;
63 } else {
64 report.mismatch.push(VerifyMismatch {
65 key: key.clone(),
66 filename: m.filename.clone(),
67 expected: m.hash.clone(),
68 actual,
69 });
70 }
71 }
72
73 Ok(report)
74}
75