Skip to main content

conduit_cli/core/project/
verify.rs

1use 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