use super::super::encode::seats_into;
use super::super::establish::coverage_issues;
use super::{
AnsweredOutput, EXPLANATION_ISSUE_LIMIT, ExplanationError, ExplanationIssue, UniversalAnswer,
UniversalQuestion, View,
};
use crate::bounded::{Bounded, Capped, Capping, NonEmpty, Overflow};
use crate::closure::Closure;
use crate::identity::{
self, ClosureId, ExplanationId, PlanId, Provenance, Transcript, encode_bytes,
};
use crate::kind::{Answer, Kind, Question, Role};
use crate::plan::Plan;
use crate::render::RenderedProjection;
use core::marker::PhantomData;
fn refused(issues: Vec<ExplanationIssue>) -> Option<ExplanationError> {
let mut established = issues.into_iter();
let first = established.next()?;
Some(ExplanationError::over(first, established.collect()))
}
impl AnsweredOutput {
pub(crate) fn roster<R: Role>(rendered: &RenderedProjection<R>) -> Vec<Self> {
R::ALL
.iter()
.copied()
.filter_map(|role| rendered.under(role))
.map(|unit| Self {
output: Box::new(unit.reconstructed().output),
digest: unit.digest(),
})
.collect()
}
}
fn outputs_beside_proof<R: Role>(
universal: &[UniversalAnswer],
closure: &Closure<R>,
) -> Vec<ExplanationIssue> {
let supplied = universal.iter().find_map(|answer| match answer {
UniversalAnswer::OutputAndDigest { outputs } => Some(outputs),
UniversalAnswer::Kind { .. }
| UniversalAnswer::Owner { .. }
| UniversalAnswer::CausingDeclarations { .. }
| UniversalAnswer::Profile { .. }
| UniversalAnswer::Assumptions { .. }
| UniversalAnswer::Invalidators { .. }
| UniversalAnswer::RelatedDispositions { .. }
| UniversalAnswer::Repairs { .. } => None,
});
let Some(supplied) = supplied else {
return Vec::new();
};
let lawful = AnsweredOutput::roster(closure.rendered());
if supplied.len() == lawful.len() && supplied.iter().eq(lawful.iter()) {
return Vec::new();
}
let diverges = supplied
.iter()
.zip(lawful.iter())
.position(|(offered, proved)| offered != proved)
.unwrap_or_else(|| supplied.len().min(lawful.len()));
vec![ExplanationIssue::OutputsBesideTheProof {
expected: u16::try_from(lawful.len()).unwrap_or(u16::MAX),
observed: u16::try_from(supplied.len()).unwrap_or(u16::MAX),
diverges: u16::try_from(diverges).unwrap_or(u16::MAX),
}]
}
fn in_roster_order<Q: Question>(answers: Vec<Q::Answer>) -> Vec<Q::Answer> {
let mut supplied: Vec<Option<Q::Answer>> = answers.into_iter().map(Some).collect();
let mut ordered: Vec<Q::Answer> = Vec::with_capacity(supplied.len());
for question in Q::ALL {
let seated = supplied.iter_mut().find(|held| {
held.as_ref()
.is_some_and(|answer| answer.question() == *question)
});
if let Some(answer) = seated.and_then(Option::take) {
ordered.push(answer);
}
}
ordered
}
impl ExplanationError {
pub fn of(issue: ExplanationIssue) -> Self {
Self {
body: Capped::all(NonEmpty::one(issue)),
}
}
pub fn over(first: ExplanationIssue, rest: Vec<ExplanationIssue>) -> Self {
Self {
body: Capped::first_n(first, rest.into_iter()),
}
}
pub fn bounded(overflow: Overflow) -> Self {
Self::of(ExplanationIssue::SeatBoundExceeded {
bound: u64::try_from(overflow.capacity).unwrap_or(u64::MAX),
observed: u64::try_from(overflow.offered).unwrap_or(u64::MAX),
})
}
#[must_use]
pub fn first_issue(&self) -> &ExplanationIssue {
self.body.items().first()
}
#[must_use]
pub fn issues(&self) -> &NonEmpty<ExplanationIssue, EXPLANATION_ISSUE_LIMIT> {
self.body.items()
}
#[must_use]
pub const fn capping(&self) -> Capping {
self.body.capping()
}
}
impl<K: Kind> View<K> {
pub fn complete(
plan: &Plan<K>,
closure: &Closure<K::Role>,
universal: Vec<UniversalAnswer>,
declared: Vec<<K::Question as Question>::Answer>,
) -> Result<Self, ExplanationError> {
let mut issues = coverage_issues::<K>(&universal, &declared);
issues.extend(outputs_beside_proof(&universal, closure));
if let Some(refusal) = refused(issues) {
return Err(refusal);
}
let seated_universal = in_roster_order::<UniversalQuestion>(universal);
let seated_declared = in_roster_order::<K::Question>(declared);
let plan_identity = plan.identity();
let closure_identity = closure.identity();
let mut content = Vec::new();
encode_bytes(plan_identity.as_bytes(), &mut content);
seats_into(&seated_universal, &mut content);
seats_into(&seated_declared, &mut content);
let (derived, provenance) =
ExplanationId::derived_with_provenance(Transcript::under_projection(
identity::Role::Explanation,
&closure_identity,
&content,
0,
));
let held_universal = Bounded::new(seated_universal).map_err(ExplanationError::bounded)?;
let held_declared = Bounded::new(seated_declared).map_err(ExplanationError::bounded)?;
Ok(Self {
plan: plan_identity,
closure: closure_identity,
universal: held_universal,
declared: held_declared,
identity: derived,
provenance,
kind: PhantomData,
})
}
#[must_use]
pub const fn identity(&self) -> ExplanationId {
self.identity
}
#[must_use]
pub const fn provenance(&self) -> &Provenance {
&self.provenance
}
#[must_use]
pub const fn plan(&self) -> PlanId {
self.plan
}
#[must_use]
pub const fn closure(&self) -> ClosureId {
self.closure
}
#[must_use]
pub fn universal(&self) -> &[UniversalAnswer] {
self.universal.as_slice()
}
#[must_use]
pub fn declared(&self) -> &[<K::Question as Question>::Answer] {
self.declared.as_slice()
}
#[must_use]
pub fn seats(&self) -> usize {
self.universal.len().saturating_add(self.declared.len())
}
}