cargo-allow 0.1.11

Source-tree exception ledger and policy scanner for Rust repositories.
use allow_core::{Finding, FindingKind, Selector, normalize_path};

pub(crate) fn selector_from_finding(finding: &Finding) -> Selector {
    Selector {
        ast_kind: Some(finding.identity.ast_kind.clone()),
        container: finding.identity.container.clone(),
        callee: finding.identity.callee.clone(),
        macro_name: finding.identity.macro_name.clone(),
        lint: finding.identity.lint.clone(),
        symbol: finding.identity.symbol.clone(),
        receiver_fingerprint: finding.identity.receiver_fingerprint.clone(),
        target_fingerprint: finding.identity.target_fingerprint.clone(),
        normalized_snippet_hash: finding.identity.normalized_snippet_hash.clone(),
        line_hint: finding.span.as_ref().map(|s| s.line),
        glob: matches!(
            finding.kind,
            FindingKind::NonRustFile | FindingKind::GeneratedCode
        )
        .then(|| normalize_path(&finding.path)),
    }
}

#[cfg(test)]
mod tests {
    use super::selector_from_finding;
    use allow_core::{Finding, FindingKind, Selector, Span, StructuralIdentity};
    use std::path::PathBuf;

    fn finding(kind: FindingKind, path: &str, identity: StructuralIdentity) -> Finding {
        Finding {
            kind,
            family: Some("fixture".to_string()),
            path: PathBuf::from(path),
            span: Some(Span {
                line: 42,
                column: 7,
            }),
            identity,
            message: "fixture".to_string(),
            ledger: None,
        }
    }

    #[test]
    fn selector_from_finding_preserves_structural_identity_fields() {
        let mut identity = StructuralIdentity::new("rust", "method_call");
        identity.container = Some("Parser::parse".to_string());
        identity.callee = Some("unwrap".to_string());
        identity.macro_name = Some("debug_assert".to_string());
        identity.lint = Some("clippy::unwrap_used".to_string());
        identity.symbol = Some("value.unwrap()".to_string());
        identity.receiver_fingerprint = Some("recv:value".to_string());
        identity.target_fingerprint = Some("target:policy".to_string());
        identity.normalized_snippet_hash = Some("fnv1a64:abc".to_string());
        identity.line_hint = Some(41);

        let selector = selector_from_finding(&finding(FindingKind::Panic, "src/lib.rs", identity));

        assert_eq!(
            selector,
            Selector {
                ast_kind: Some("method_call".to_string()),
                container: Some("Parser::parse".to_string()),
                callee: Some("unwrap".to_string()),
                macro_name: Some("debug_assert".to_string()),
                lint: Some("clippy::unwrap_used".to_string()),
                symbol: Some("value.unwrap()".to_string()),
                receiver_fingerprint: Some("recv:value".to_string()),
                target_fingerprint: Some("target:policy".to_string()),
                normalized_snippet_hash: Some("fnv1a64:abc".to_string()),
                line_hint: Some(42),
                glob: None,
            }
        );
    }

    #[test]
    fn selector_from_finding_adds_glob_only_for_file_inventory_findings() {
        let cases = [
            (
                FindingKind::NonRustFile,
                "docs/README.md",
                Some("docs/README.md"),
            ),
            (
                FindingKind::GeneratedCode,
                "target/generated.rs",
                Some("target/generated.rs"),
            ),
            (FindingKind::Unsafe, "src/lib.rs", None),
        ];

        for (kind, path, expected_glob) in cases {
            let selector = selector_from_finding(&finding(
                kind,
                path,
                StructuralIdentity::new("rust", "file"),
            ));

            assert_eq!(selector.ast_kind.as_deref(), Some("file"));
            assert_eq!(selector.line_hint, Some(42));
            assert_eq!(selector.glob.as_deref(), expected_glob);
        }
    }
}