use super::policy::{AnalysisPolicy, Fingerprint, PolicyField};
use super::provenance::Provenance;
use crate::diagnostic::Diagnostic;
use std::ops::Deref;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum Status {
#[default]
Complete,
Partial,
Ambiguous,
Indeterminate,
}
impl Status {
#[must_use]
pub const fn is_usable(self) -> bool {
!matches!(self, Self::Indeterminate)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Coverage {
pub intended: u32,
pub used: u32,
pub missing: u32,
pub ambiguous: u32,
}
impl Coverage {
#[must_use]
pub const fn complete(atoms: u32) -> Self {
Self {
intended: atoms,
used: atoms,
missing: 0,
ambiguous: 0,
}
}
#[must_use]
pub fn fraction(self) -> f64 {
if self.intended == 0 {
1.0
} else {
f64::from(self.used) / f64::from(self.intended)
}
}
#[must_use]
pub const fn is_complete(self) -> bool {
self.missing == 0 && self.ambiguous == 0 && self.used == self.intended
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[non_exhaustive]
pub enum ImpactEstimate {
None,
Low,
Moderate,
High,
#[default]
Unknown,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum AssumptionSource {
Explicit,
ProfileDefault,
Inferred,
}
#[derive(Clone, PartialEq, Debug)]
pub struct Assumption {
pub field: PolicyField,
pub value: Box<str>,
pub source: AssumptionSource,
pub impact: ImpactEstimate,
}
impl Assumption {
#[must_use]
pub fn new(
field: PolicyField,
value: impl Into<Box<str>>,
source: AssumptionSource,
impact: ImpactEstimate,
) -> Self {
Self {
field,
value: value.into(),
source,
impact,
}
}
#[must_use]
pub fn is_silent_and_material(&self) -> bool {
self.source == AssumptionSource::ProfileDefault
&& self.impact >= ImpactEstimate::Moderate
&& self.impact != ImpactEstimate::Unknown
}
}
#[derive(Clone, Debug)]
pub struct Analysis<T> {
pub value: T,
pub status: Status,
pub coverage: Coverage,
pub warnings: Vec<Diagnostic>,
pub assumptions: Vec<Assumption>,
pub provenance: Provenance,
}
impl<T> Analysis<T> {
#[must_use]
pub fn complete(value: T, coverage: Coverage, policy: &AnalysisPolicy) -> Self {
Self {
value,
status: Status::Complete,
coverage,
warnings: Vec::new(),
assumptions: Vec::new(),
provenance: Provenance::new(policy),
}
}
#[must_use]
pub fn partial(value: T, coverage: Coverage, policy: &AnalysisPolicy) -> Self {
Self {
status: Status::Partial,
..Self::complete(value, coverage, policy)
}
}
#[must_use]
pub fn indeterminate(value: T, coverage: Coverage, policy: &AnalysisPolicy) -> Self {
Self {
status: Status::Indeterminate,
..Self::complete(value, coverage, policy)
}
}
#[must_use]
pub fn with_warning(mut self, warning: Diagnostic) -> Self {
self.warnings.push(warning);
self
}
#[must_use]
pub fn with_assumption(mut self, assumption: Assumption) -> Self {
self.assumptions.push(assumption);
self
}
#[must_use]
pub fn policy_fingerprint(&self) -> Fingerprint {
self.provenance.policy_fingerprint
}
pub fn silent_assumptions(&self) -> impl Iterator<Item = &Assumption> {
self.assumptions
.iter()
.filter(|assumption| assumption.is_silent_and_material())
}
pub fn map<U>(self, transform: impl FnOnce(T) -> U) -> Analysis<U> {
Analysis {
value: transform(self.value),
status: self.status,
coverage: self.coverage,
warnings: self.warnings,
assumptions: self.assumptions,
provenance: self.provenance,
}
}
}
impl<T> Deref for Analysis<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
#[cfg(test)]
#[path = "analysis_tests.rs"]
mod tests;