use std::collections::{HashMap, HashSet};
use std::fmt;
use super::{Place, TestFiles, Trace, TraceRule, places, reconcile};
use crate::document::Document;
use crate::hash::DocumentHashes;
use crate::identity::Key;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Refusal {
NoNode(Key),
NoLink(Key),
Finding(Key, TraceRule),
}
impl Refusal {
#[must_use]
pub const fn key(&self) -> Key {
match self {
Self::NoNode(key) | Self::NoLink(key) | Self::Finding(key, _) => *key,
}
}
}
impl fmt::Display for Refusal {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoNode(_) => formatter.write_str("no node of the tree carries this key"),
Self::NoLink(_) => formatter.write_str("no link names this node"),
Self::Finding(_, rule) => write!(formatter, "the link has a {rule} finding"),
}
}
}
pub fn accept(
trace: &mut Trace,
keys: &[Key],
document: &Document,
hashes: &DocumentHashes,
files: &impl TestFiles,
) -> Result<(), Vec<Refusal>> {
let places = places(document);
let untrue = untrue(trace, document, hashes, files);
let linked: HashSet<Key> = trace.links.iter().map(|link| link.node).collect();
let refusals: Vec<Refusal> = keys
.iter()
.filter_map(|&key| {
if !places.contains_key(&key) {
Some(Refusal::NoNode(key))
} else if !linked.contains(&key) {
Some(Refusal::NoLink(key))
} else {
untrue.get(&key).map(|&rule| Refusal::Finding(key, rule))
}
})
.collect();
if !refusals.is_empty() {
return Err(refusals);
}
stamp(trace, keys, &places, hashes);
Ok(())
}
pub fn accept_stale(
trace: &mut Trace,
document: &Document,
hashes: &DocumentHashes,
files: &impl TestFiles,
) -> Vec<Key> {
let reconciled = reconcile(document, hashes, trace, files);
let mut asks_review = HashSet::new();
let mut refused = HashSet::new();
for finding in &reconciled.findings {
if finding.rule.asks_review() {
asks_review.insert(finding.node);
} else if finding.rule.refuses_accept() {
refused.insert(finding.node);
}
}
let stale: Vec<Key> = trace
.links
.iter()
.map(|link| link.node)
.filter(|key| asks_review.contains(key) && !refused.contains(key))
.collect();
stamp(trace, &stale, &places(document), hashes);
stale
}
fn stamp(trace: &mut Trace, keys: &[Key], places: &HashMap<Key, Place>, hashes: &DocumentHashes) {
let keys: HashSet<&Key> = keys.iter().collect();
for link in trace
.links
.iter_mut()
.filter(|link| keys.contains(&link.node))
{
link.reviewed = Some(hashes.nodes[&places[&link.node].id]);
}
}
fn untrue(
trace: &Trace,
document: &Document,
hashes: &DocumentHashes,
files: &impl TestFiles,
) -> HashMap<Key, TraceRule> {
let mut untrue = HashMap::new();
for finding in reconcile(document, hashes, trace, files).findings {
if finding.rule.refuses_accept() {
untrue.entry(finding.node).or_insert(finding.rule);
}
}
untrue
}