use std::collections::BTreeMap;
use std::fmt;
use super::{Outcome, Provenance};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct LedgerLimits {
pub repeat: u32,
pub distinct: usize,
}
impl LedgerLimits {
#[must_use]
pub const fn new(repeat: u32, distinct: usize) -> Self {
Self { repeat, distinct }
}
}
impl Default for LedgerLimits {
fn default() -> Self {
Self {
repeat: 3,
distinct: 16,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Intervention {
NoProgress {
fingerprint: String,
repetitions: u32,
},
LedgerFull {
held: usize,
},
}
impl Intervention {
#[must_use]
pub const fn label(&self) -> &'static str {
match *self {
Self::NoProgress { .. } => "NoProgress",
Self::LedgerFull { .. } => "LedgerFull",
}
}
}
impl std::error::Error for Intervention {}
impl fmt::Display for Intervention {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::NoProgress {
ref fingerprint,
ref repetitions,
} => write!(
formatter,
"no progress: {fingerprint:?} failed {repetitions} times unchanged, so no \
further repair is admitted"
),
Self::LedgerFull { held } => write!(
formatter,
"ledger full: {held} distinct failures recorded, so none further is retained"
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Tracked {
repetitions: u32,
provenance: Provenance,
progress: bool,
}
#[derive(Debug, Clone)]
pub struct RepairLedger {
limits: LedgerLimits,
provenance: Provenance,
tracked: BTreeMap<String, Tracked>,
spent: u64,
evidence: u64,
}
impl RepairLedger {
#[must_use]
pub fn new(limits: LedgerLimits, provenance: Provenance) -> Self {
Self {
limits,
provenance,
tracked: BTreeMap::new(),
spent: 0,
evidence: 0,
}
}
#[must_use]
pub const fn limits(&self) -> LedgerLimits {
self.limits
}
#[must_use]
pub const fn provenance(&self) -> &Provenance {
&self.provenance
}
#[must_use]
pub const fn spent(&self) -> u64 {
self.spent
}
#[must_use]
pub const fn evidence(&self) -> u64 {
self.evidence
}
#[must_use]
pub fn repetitions(&self, fingerprint: &str) -> u32 {
self.tracked
.get(fingerprint)
.map_or(0, |tracked| tracked.repetitions)
}
#[must_use]
pub fn provenance_of(&self, fingerprint: &str) -> Option<&Provenance> {
self.tracked
.get(fingerprint)
.map(|tracked| &tracked.provenance)
}
#[must_use]
pub fn record_failure(&mut self, fingerprint: &str) -> Outcome {
self.spent = self.spent.saturating_add(1);
if let Some(tracked) = self.tracked.get_mut(fingerprint) {
tracked.repetitions = tracked.repetitions.saturating_add(1);
let repetitions = tracked.repetitions;
return if repetitions > self.limits.repeat {
Outcome::Intervention(Intervention::NoProgress {
fingerprint: fingerprint.to_owned(),
repetitions,
})
} else {
Outcome::Admitted {
plan: super::Plan::for_repair(fingerprint, self.provenance.clone()),
provenance: self.provenance.clone(),
}
};
}
if self.tracked.len() >= self.limits.distinct {
return Outcome::Intervention(Intervention::LedgerFull {
held: self.tracked.len(),
});
}
self.tracked.insert(
fingerprint.to_owned(),
Tracked {
repetitions: 1,
provenance: self.provenance.clone(),
progress: false,
},
);
Outcome::Admitted {
plan: super::Plan::for_repair(fingerprint, self.provenance.clone()),
provenance: self.provenance.clone(),
}
}
pub fn record_evidence(&mut self, fingerprint: &str) -> bool {
let Some(tracked) = self.tracked.get_mut(fingerprint) else {
return false;
};
self.evidence = self.evidence.saturating_add(1);
tracked.progress = true;
true
}
#[must_use]
pub fn has_progress(&self, fingerprint: &str) -> bool {
self.tracked
.get(fingerprint)
.is_some_and(|tracked| tracked.progress)
}
#[must_use]
pub fn may_repair(&self, fingerprint: &str) -> bool {
self.repetitions(fingerprint) <= self.limits.repeat
&& (self.tracked.contains_key(fingerprint) || self.tracked.len() < self.limits.distinct)
}
}