use crate::journal::frame::SaturatingFrom;
use std::fmt;
use super::{Intervention, Outcome, Provenance, Refusal};
pub const MAX_EVIDENCE_REFS: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Coverage {
Partial {
covered: u64,
asked: u64,
},
Complete,
}
impl Coverage {
#[must_use]
pub fn from_claim(claim: Option<&str>) -> Self {
match claim {
Some("complete") => Self::Partial {
covered: 1,
asked: 1,
},
_ => Self::Partial {
covered: 0,
asked: 1,
},
}
}
#[must_use]
pub const fn is_complete(&self) -> bool {
matches!(self, Self::Complete)
}
#[must_use]
pub const fn counts(&self) -> (u64, u64) {
match *self {
Self::Partial { covered, asked } => (covered, asked),
Self::Complete => (1, 1),
}
}
#[must_use]
pub const fn label(&self) -> &'static str {
match *self {
Self::Partial { .. } => "Partial",
Self::Complete => "Complete",
}
}
}
impl fmt::Display for Coverage {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Partial { covered, asked } => write!(formatter, "{covered}/{asked}"),
Self::Complete => formatter.write_str("complete"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CompletionKind {
Finished,
Abandoned,
}
impl CompletionKind {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Finished => "Finished",
Self::Abandoned => "Abandoned",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Completion {
kind: CompletionKind,
summary: String,
named: Vec<String>,
}
impl Completion {
#[must_use]
pub fn claim(summary: &str, named: &[&str]) -> Self {
Self {
kind: CompletionKind::Finished,
summary: summary.to_owned(),
named: named.iter().map(|name| (*name).to_owned()).collect(),
}
}
#[must_use]
pub fn abandon(summary: &str) -> Self {
Self {
kind: CompletionKind::Abandoned,
summary: summary.to_owned(),
named: Vec::new(),
}
}
#[must_use]
pub const fn kind(&self) -> CompletionKind {
self.kind
}
#[must_use]
pub fn summary(&self) -> &str {
&self.summary
}
#[must_use]
pub fn named(&self) -> &[String] {
&self.named
}
pub fn admit(&self, present: &[String]) -> Result<Coverage, Refusal> {
if self.named.len() > MAX_EVIDENCE_REFS {
let refusal = Err(Refusal::Limit {
what: "the number of evidence references a completion claim names",
got: u64::saturating_from(self.named.len()),
limit: u64::saturating_from(MAX_EVIDENCE_REFS),
});
lgwks_std::trace::debug!(error = ?refusal.as_ref().err(), "admit: returning an error to the caller");
return refusal;
}
let missing = self
.named
.iter()
.filter(|reference| !present.contains(reference))
.count();
if missing == 0 {
Ok(Coverage::Complete)
} else {
Err(Refusal::NotEvidenced {
named: self.named.clone(),
missing,
})
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CompletionOutcome {
Admitted {
kind: CompletionKind,
coverage: Coverage,
},
NotEvidenced {
refusal: Box<Refusal>,
provenance: Option<Provenance>,
},
Incomplete {
coverage: Coverage,
},
Intervention(Intervention),
}
impl CompletionOutcome {
#[must_use]
pub const fn is_admitted(&self) -> bool {
matches!(self, Self::Admitted { .. })
}
#[must_use]
pub fn refusal(&self) -> Option<&Refusal> {
match *self {
Self::NotEvidenced { ref refusal, .. } => Some(refusal.as_ref()),
Self::Admitted { .. } | Self::Incomplete { .. } | Self::Intervention(_) => None,
}
}
#[must_use]
pub fn settle(claim: &Completion, present: &[String], coverage: Coverage) -> Self {
if claim.kind() == CompletionKind::Abandoned {
return Self::Admitted {
kind: claim.kind(),
coverage,
};
}
match claim.admit(present) {
Err(refusal) => Self::NotEvidenced {
refusal: Box::new(refusal),
provenance: None,
},
Ok(_) if !coverage.is_complete() => Self::Incomplete { coverage },
Ok(complete) => Self::Admitted {
kind: claim.kind(),
coverage: complete,
},
}
}
#[must_use]
pub fn from(&self, provenance: Provenance) -> Self {
match *self {
Self::NotEvidenced {
ref refusal,
provenance: _,
} => Self::NotEvidenced {
refusal: refusal.clone(),
provenance: Some(provenance),
},
Self::Admitted {
ref kind,
ref coverage,
} => Self::Admitted {
kind: *kind,
coverage: *coverage,
},
Self::Incomplete { ref coverage } => Self::Incomplete {
coverage: *coverage,
},
Self::Intervention(ref intervention) => Self::Intervention(intervention.clone()),
}
}
#[must_use]
pub fn from_outcome(outcome: &Outcome) -> Self {
match *outcome {
Outcome::Refused {
ref refusal,
ref provenance,
} => Self::NotEvidenced {
refusal: refusal.clone(),
provenance: Some(provenance.clone()),
},
Outcome::Intervention(ref intervention) => Self::Intervention(intervention.clone()),
Outcome::Admitted { .. } => Self::Incomplete {
coverage: Coverage::Partial {
covered: 0,
asked: 1,
},
},
}
}
}
impl fmt::Display for CompletionOutcome {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Admitted { kind, coverage } => {
write!(formatter, "admitted {} at {coverage}", kind.label())
}
Self::NotEvidenced {
ref refusal,
provenance: Some(ref provenance),
} => write!(formatter, "not evidenced: {refusal} (from {provenance})"),
Self::NotEvidenced {
ref refusal,
provenance: None,
} => write!(formatter, "not evidenced: {refusal}"),
Self::Incomplete { coverage } => write!(formatter, "incomplete: {coverage}"),
Self::Intervention(ref intervention) => write!(formatter, "{intervention}"),
}
}
}