use super::{
ExplainContext,
explain_steps::{ExplainReferenceAttention, explain_next_steps},
};
use crate::evidence_inventory::{
DEFAULT_SOURCE_TREE_INVENTORY_EVIDENCE_MESSAGE, evidence_reference_diagnostics_for_source_tree,
policy_reference_diagnostics_for_source_tree,
};
use crate::evidence_render::evidence_reference_target_text;
use allow_core::{AllowEntry, Finding, MatchOutcome, allow_entry_broad_scope};
use allow_diff::selector_precision_score;
use std::collections::BTreeSet;
use std::path::Path;
pub(super) fn render_explain_entry(
root: &Path,
entry: &AllowEntry,
findings: &[Finding],
outcomes: &[MatchOutcome],
evidence_source_tree_files: Option<&BTreeSet<String>>,
) -> String {
render_explain_report(
root,
entry,
findings,
outcomes,
evidence_source_tree_files,
ExplainContext::default(),
allow_report::render_explain_human,
)
}
pub(super) fn render_explain_entry_json(
root: &Path,
entry: &AllowEntry,
findings: &[Finding],
outcomes: &[MatchOutcome],
evidence_source_tree_files: Option<&BTreeSet<String>>,
context: ExplainContext<'_>,
) -> String {
render_explain_report(
root,
entry,
findings,
outcomes,
evidence_source_tree_files,
context,
allow_report::render_explain_json,
)
}
fn render_explain_report<R>(
root: &Path,
entry: &AllowEntry,
findings: &[Finding],
outcomes: &[MatchOutcome],
evidence_source_tree_files: Option<&BTreeSet<String>>,
context: ExplainContext<'_>,
render: impl FnOnce(allow_report::ExplainReport<'_>) -> R,
) -> R {
let evidence_diagnostics =
evidence_reference_diagnostics_for_source_tree(root, entry, evidence_source_tree_files);
let link_diagnostics =
link_reference_diagnostics_for_source_tree(root, entry, evidence_source_tree_files);
let references = ExplainReferenceAttention {
has_broken_evidence: evidence_diagnostics
.iter()
.any(|diagnostic| diagnostic.status.is_broken_local_link()),
has_weak_evidence: evidence_diagnostics
.iter()
.any(|diagnostic| diagnostic.status.is_weak_reference()),
has_evidence_outside_default_inventory: evidence_diagnostics
.iter()
.any(|diagnostic| diagnostic.message == DEFAULT_SOURCE_TREE_INVENTORY_EVIDENCE_MESSAGE),
has_broken_link: link_diagnostics
.iter()
.any(|diagnostic| diagnostic.status.is_broken_local_link()),
has_weak_link: link_diagnostics
.iter()
.any(|diagnostic| diagnostic.status.is_weak_reference()),
has_link_outside_default_inventory: link_diagnostics
.iter()
.any(|diagnostic| diagnostic.message == DEFAULT_SOURCE_TREE_INVENTORY_EVIDENCE_MESSAGE),
};
let (suggested_actions, proof_commands) =
explain_next_steps(entry, findings, outcomes, references);
let normalized_targets = evidence_diagnostics
.iter()
.map(evidence_reference_target_text)
.collect::<Vec<_>>();
let evidence_references = evidence_diagnostics
.iter()
.zip(normalized_targets.iter())
.map(|(diagnostic, target)| allow_report::EvidenceReference {
raw: &diagnostic.raw,
prefix: diagnostic.prefix.as_deref(),
target: target.as_deref(),
status: diagnostic.status.as_str(),
category: diagnostic.category.as_str(),
message: &diagnostic.message,
})
.collect::<Vec<_>>();
let link_normalized_targets = link_diagnostics
.iter()
.map(evidence_reference_target_text)
.collect::<Vec<_>>();
let link_messages = link_diagnostics
.iter()
.map(|diagnostic| link_reference_message(&diagnostic.message))
.collect::<Vec<_>>();
let link_references = link_diagnostics
.iter()
.zip(link_normalized_targets.iter())
.zip(link_messages.iter())
.map(
|((diagnostic, target), message)| allow_report::EvidenceReference {
raw: &diagnostic.raw,
prefix: diagnostic.prefix.as_deref(),
target: target.as_deref(),
status: diagnostic.status.as_str(),
category: diagnostic.category.as_str(),
message,
},
)
.collect::<Vec<_>>();
render(allow_report::ExplainReport {
inventory: context.inventory,
entry,
selector_precision: selector_precision_score(entry),
broad_scope: allow_entry_broad_scope(entry).is_some(),
current_findings: findings,
match_outcomes: outcomes,
evidence_references: &evidence_references,
link_references: &link_references,
suggested_actions: &suggested_actions,
proof_commands: &proof_commands,
})
}
fn link_reference_diagnostics_for_source_tree(
root: &Path,
entry: &AllowEntry,
evidence_source_tree_files: Option<&BTreeSet<String>>,
) -> Vec<allow_policy::EvidenceReferenceDiagnostic> {
policy_reference_diagnostics_for_source_tree(root, entry, evidence_source_tree_files)
.into_iter()
.filter(|reference| reference.source == crate::evidence_inventory::ReferenceSource::Link)
.map(|reference| reference.diagnostic)
.collect()
}
fn link_reference_message(message: &str) -> String {
message.replace("evidence", "link")
}