allow-report 0.1.9

Report and receipt rendering for cargo-allow source exception scans.
Documentation
use allow_core::StructuralIdentity;

pub(crate) fn structural_identity_summary(identity: &StructuralIdentity) -> String {
    let mut fields = Vec::new();
    push_required(&mut fields, "ast_kind", &identity.ast_kind);
    push_optional(&mut fields, "module", identity.module.as_deref());
    push_optional(&mut fields, "container", identity.container.as_deref());
    push_optional(&mut fields, "callee", identity.callee.as_deref());
    push_optional(&mut fields, "macro_name", identity.macro_name.as_deref());
    push_optional(&mut fields, "lint", identity.lint.as_deref());
    push_optional(&mut fields, "symbol", identity.symbol.as_deref());
    push_optional(
        &mut fields,
        "receiver_fingerprint",
        identity.receiver_fingerprint.as_deref(),
    );
    push_optional(
        &mut fields,
        "target_fingerprint",
        identity.target_fingerprint.as_deref(),
    );
    push_optional(
        &mut fields,
        "normalized_snippet_hash",
        identity.normalized_snippet_hash.as_deref(),
    );
    fields.join(",")
}

fn push_required(fields: &mut Vec<String>, name: &str, value: &str) {
    let trimmed = value.trim();
    if !trimmed.is_empty() {
        fields.push(format!("{name}={trimmed}"));
    }
}

fn push_optional(fields: &mut Vec<String>, name: &str, value: Option<&str>) {
    if let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) {
        fields.push(format!("{name}={value}"));
    }
}