use super::super::encode::claim;
use super::super::prove::{addressed, examined, units_to};
use super::{
CLOSURE_ISSUE_LIMIT, CarriedTokens, Closure, ClosureError, ClosureIssue, PartitionCargo,
PartitionedEmission,
};
use crate::bounded::{Capped, Capping, NonEmpty};
use crate::identity::{self, ClosureId, Identity, PlanId, Provenance, Transcript};
use crate::kind::{Destination, Kind, Role};
use crate::plan::{Membership, Plan, PlannedMember};
use crate::render::RenderedProjection;
use crate::token::GeneratedTree;
impl CarriedTokens {
fn joined(plan: PlanId, destination: Destination, tree: GeneratedTree) -> Self {
let raw = tree.canonical_bytes();
let digest = Identity::derived(Transcript::under_projection(
identity::Role::OutputBytes,
&plan,
&raw,
delivery_position(destination),
));
Self { tree, digest }
}
#[must_use]
pub const fn tree(&self) -> &GeneratedTree {
&self.tree
}
#[must_use]
pub const fn digest(&self) -> Identity<identity::OutputBytes> {
self.digest
}
}
impl PartitionCargo {
#[must_use]
pub const fn tokens(&self) -> Option<&GeneratedTree> {
match self {
Self::NothingPlanned => None,
Self::Carried(carried) => Some(carried.tree()),
}
}
}
impl PartitionedEmission {
fn over<R: Role>(
plan: PlanId,
rendered: &RenderedProjection<R>,
) -> Result<Self, ClosureIssue<R>> {
Ok(Self {
declaration_site: joined_cargo(plan, rendered, Destination::DeclarationSite)?,
test_carrier: joined_cargo(plan, rendered, Destination::TestCarrier)?,
bench_carrier: joined_cargo(plan, rendered, Destination::BenchCarrier)?,
})
}
pub const fn declaration_site(&self) -> &PartitionCargo {
&self.declaration_site
}
pub const fn test_carrier(&self) -> &PartitionCargo {
&self.test_carrier
}
pub const fn bench_carrier(&self) -> &PartitionCargo {
&self.bench_carrier
}
#[must_use]
pub const fn joined(&self, destination: Destination) -> Option<&PartitionCargo> {
match destination {
Destination::DeclarationSite => Some(&self.declaration_site),
Destination::TestCarrier => Some(&self.test_carrier),
Destination::BenchCarrier => Some(&self.bench_carrier),
Destination::PublicationArtifact => None,
}
}
}
impl<R: Role> ClosureError<R> {
pub fn of(issue: ClosureIssue<R>) -> Self {
Self {
body: Capped::all(NonEmpty::one(issue)),
}
}
pub fn over(first: ClosureIssue<R>, rest: Vec<ClosureIssue<R>>) -> Self {
Self {
body: Capped::first_n(first, rest.into_iter()),
}
}
#[must_use]
pub fn first_issue(&self) -> &ClosureIssue<R> {
self.body.items().first()
}
#[must_use]
pub fn issues(&self) -> &NonEmpty<ClosureIssue<R>, CLOSURE_ISSUE_LIMIT> {
self.body.items()
}
#[must_use]
pub const fn capping(&self) -> Capping {
self.body.capping()
}
}
impl<R: Role> Closure<R> {
pub fn proved<K: Kind<Role = R>>(
plan: &Plan<K>,
rendered: RenderedProjection<R>,
) -> Result<Self, ClosureError<R>> {
let planned = plan.membership();
let named = plan.identity();
let (issues, rows) = examined(planned, &rendered);
if let Some(refusal) = refused(issues) {
return Err(refusal);
}
let reconstructed = rebuilt(rows)?;
let disagreements: Vec<ClosureIssue<R>> = R::ALL
.iter()
.copied()
.filter(|role| !reconstructed.agrees_under(planned, *role))
.map(|role| ClosureIssue::MembershipDisagreement { role })
.collect();
if let Some(refusal) = refused(disagreements) {
return Err(refusal);
}
if let Some(refusal) = refused(addressed(&rendered)) {
return Err(refusal);
}
let emission = PartitionedEmission::over(named, &rendered).map_err(ClosureError::of)?;
let material = claim(planned, &rendered, &emission);
let (derived, provenance) = ClosureId::derived_with_provenance(
Transcript::under_projection(identity::Role::Closure, &named, &material, 0),
);
Ok(Self {
plan: named,
reconstructed,
rendered,
emission,
identity: derived,
provenance,
})
}
#[must_use]
pub const fn reconstructed(&self) -> &Membership<R> {
&self.reconstructed
}
#[must_use]
pub const fn rendered(&self) -> &RenderedProjection<R> {
&self.rendered
}
pub(crate) const fn emission(&self) -> &PartitionedEmission {
&self.emission
}
#[must_use]
pub const fn plan(&self) -> PlanId {
self.plan
}
#[must_use]
pub const fn identity(&self) -> ClosureId {
self.identity
}
#[must_use]
pub const fn provenance(&self) -> &Provenance {
&self.provenance
}
}
fn refused<R: Role>(issues: Vec<ClosureIssue<R>>) -> Option<ClosureError<R>> {
let mut established = issues.into_iter();
let first = established.next()?;
Some(ClosureError::over(first, established.collect()))
}
fn rebuilt<R: Role>(rows: Vec<PlannedMember<R>>) -> Result<Membership<R>, ClosureError<R>> {
let observed = u32::try_from(rows.len()).unwrap_or(u32::MAX);
let mut members = rows.into_iter();
let Some(first) = members.next() else {
return Err(ClosureError::of(ClosureIssue::ReconstructionEmpty));
};
Membership::declared(first, members.collect())
.map_err(|_| ClosureError::of(ClosureIssue::ReconstructionUndeclarable { observed }))
}
fn joined_cargo<R: Role>(
plan: PlanId,
rendered: &RenderedProjection<R>,
destination: Destination,
) -> Result<PartitionCargo, ClosureIssue<R>> {
let mut joined: Option<GeneratedTree> = None;
for unit in units_to(rendered, destination) {
joined = Some(match joined {
Some(tree) => tree
.joined(unit.tree())
.map_err(|_| ClosureIssue::JoinedTreeUnbounded { destination })?,
None => unit.tree().clone(),
});
}
let Some(tree) = joined else {
return Ok(PartitionCargo::NothingPlanned);
};
Ok(PartitionCargo::Carried(CarriedTokens::joined(
plan,
destination,
tree,
)))
}
const fn delivery_position(destination: Destination) -> u32 {
match destination {
Destination::DeclarationSite => 0,
Destination::TestCarrier => 1,
Destination::BenchCarrier => 2,
Destination::PublicationArtifact => 3,
}
}