gx 0.8.4

CLI to manage Github Actions dependencies
Documentation
use super::{Context, Diagnostic, Rule, RuleName};
use crate::config::Level;
use crate::domain::action::spec::Spec;
use crate::domain::action::specifier::Specifier;
use crate::domain::action::uses_ref::ParsedRef;

/// sha-mismatch rule: detects when a workflow SHA doesn't match the lock file.
pub struct ShaMismatchRule;

impl ShaMismatchRule {
    /// Check a single action for the sha-mismatch rule.
    ///
    /// Only a bare SHA ref (`uses: owner/repo@<sha>`, no comment) is looked up as
    /// a SHA key — a `# comment` pin already carries its human version, and a
    /// plain tag/branch has no SHA to reconcile.
    pub fn check_action(
        action: &crate::domain::file::actions::Located,
        lock: &crate::domain::lock::Lock,
    ) -> Option<Diagnostic> {
        let ParsedRef::Sha(sha) = &action.action.reference else {
            return None;
        };

        let key = Spec::new(action.action.id.clone(), Specifier::from_v1(sha.as_str()));
        if lock.has(&key) {
            return None;
        }

        let msg = format!(
            "action {} SHA {} not found in lock file",
            &action.action.id,
            sha.as_str()
        );
        Some(
            Diagnostic::new(RuleName::ShaMismatch, Level::Error, msg)
                .with_workflow(action.site.file.clone())
                .with_line(action.origin.line),
        )
    }
}

impl Rule for ShaMismatchRule {
    fn name(&self) -> RuleName {
        RuleName::ShaMismatch
    }

    fn default_level(&self) -> Level {
        Level::Error
    }

    fn check(&self, ctx: &Context) -> Vec<Diagnostic> {
        ctx.workflows
            .iter()
            .filter_map(|a| Self::check_action(a, ctx.lock))
            .collect()
    }
}

#[cfg(test)]
mod tests {
    use super::{Level, Rule as _, RuleName, ShaMismatchRule};

    #[test]
    fn sha_mismatch_rule_has_correct_metadata() {
        let rule = ShaMismatchRule;
        assert_eq!(rule.name(), RuleName::ShaMismatch);
        assert_eq!(rule.default_level(), Level::Error);
    }
}