structured_ltv/material/dependency.rs
1//! Sticky, accounted dependency visitation shared by containing formats.
2
3use super::ArtifactRef;
4use crate::source::Location;
5use structured_asn1::memory::{MemoryError, OwnedVec, Work};
6
7/// One dependency from the actual credential-format owner.
8#[derive(Clone, Copy)]
9pub enum Dependency<'value, 'source, N, D, G> {
10 /// Exact certificate or status artifact, not a claimed digest/name match.
11 Known(ArtifactRef<'value, 'source>),
12 /// Actual original schema-open enclosure requiring format interpretation.
13 Open(Location<N, D, G>),
14 /// The handler established that required material is missing.
15 Missing,
16}
17/// Completed format interpretation, independent of incorporated availability.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Completeness {
20 /// All actual dependencies were visited and interpreted.
21 Complete,
22 /// The selected owner cannot interpret this required format.
23 Unsupported,
24 /// A prefix is not the complete original dependency census.
25 Unprocessed,
26 /// Readable material is unusable for this actual credential.
27 Unusable,
28 /// A required value violates its selected format syntax.
29 Malformed,
30}
31/// Original handler failure versus its supplied visitor's unchanged refusal.
32#[derive(Debug)]
33pub enum VisitError<E, V> {
34 /// Original source, resource or format-processing cause.
35 Processing(E),
36 /// The supplied dependency visitor refused.
37 Visitor(V),
38}
39/// Missing and incorporated-but-uncovered are distinct negative facts.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum Missing {
42 /// No exact original copy was incorporated.
43 Absent,
44 /// No exact original copy is inside this archive input.
45 Uncovered,
46}
47/// One original dependency's unavailable required material.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct Finding {
50 /// Zero-based original dependency visitation ordinal.
51 pub dependency: usize,
52 /// Original incorporation or coverage cause.
53 pub cause: Missing,
54}
55/// A complete visit's syntax and material findings, retaining no input copies.
56pub struct DependencyReport {
57 /// Actual handler's completed interpretation.
58 pub completeness: Completeness,
59 /// Every original missing dependency in visitation order.
60 pub findings: OwnedVec<Finding>,
61}
62/// The supplied visitor refused; its original cause remains in the collector.
63#[derive(Debug)]
64pub struct DependencyRefused;
65/// Refusal terminates the visit without returning a successful prefix.
66#[derive(Debug)]
67pub enum DependencyError<N, E, P> {
68 /// Exact-artifact/source/coverage resolver refused.
69 Processing {
70 /// Actual original credential.
71 node: N,
72 /// Original resolver cause.
73 cause: E,
74 },
75 /// Current work or retained report capacity refused before use.
76 Resource {
77 /// Actual original credential.
78 node: N,
79 /// Original resource cause.
80 cause: MemoryError,
81 },
82 /// The format owner refused with its opaque original cause.
83 Provider {
84 /// Actual original credential.
85 node: N,
86 /// Original provider cause.
87 cause: P,
88 },
89 /// A visitor refusal was returned without an actual refused observation.
90 CallbackProtocol {
91 /// Actual original credential.
92 node: N,
93 },
94}
95
96/// One synchronous visitor with a private sticky refusal. No dispatch, parser,
97/// provider, acquisition or policy is supplied by this collector. The format
98/// resolves each dependency against its own sealed whole/archive scope.
99pub struct DependencyVisit<'w, N, D, G, W: Work, F, E> {
100 node: N,
101 work: &'w W,
102 resolve: F,
103 findings: OwnedVec<Finding, W>,
104 ordinal: usize,
105 first: Option<DependencyError<N, E, core::convert::Infallible>>,
106 source: core::marker::PhantomData<(D, G)>,
107}
108impl<'w, N: Copy, D, G, W: Work, F, E> DependencyVisit<'w, N, D, G, W, F, E>
109where
110 F: for<'v, 's> FnMut(Dependency<'v, 's, N, D, G>) -> Result<Option<Missing>, E>,
111{
112 /// Metadata-only construction, with no allocation or provider call.
113 pub fn new(node: N, work: &'w W, resolve: F) -> Result<Self, MemoryError> {
114 work.charge_work(1)?;
115 Ok(Self {
116 node,
117 work,
118 resolve,
119 findings: work.vector(),
120 ordinal: 0,
121 first: None,
122 source: core::marker::PhantomData,
123 })
124 }
125 /// Admits every callback before lookup/copy. A swallowed first refusal
126 /// cannot become a complete empty report when the provider returns later.
127 pub fn observe(&mut self, need: Dependency<'_, '_, N, D, G>) -> Result<(), DependencyRefused> {
128 if self.first.is_some() {
129 return Err(DependencyRefused);
130 }
131 let resource = |cause| DependencyError::Resource {
132 node: self.node,
133 cause,
134 };
135 let result = (|| {
136 self.work.charge_work(1).map_err(resource)?;
137 let dependency = self.ordinal;
138 self.ordinal = dependency
139 .checked_add(1)
140 .ok_or_else(|| resource(MemoryError::WorkExceeded))?;
141 if let Some(cause) =
142 (self.resolve)(need).map_err(|cause| DependencyError::Processing {
143 node: self.node,
144 cause,
145 })?
146 {
147 self.findings
148 .try_push(Finding { dependency, cause })
149 .map_err(resource)?;
150 }
151 Ok(())
152 })();
153 if let Err(cause) = result {
154 self.first = Some(cause);
155 return Err(DependencyRefused);
156 }
157 Ok(())
158 }
159 /// Preserve the first original callback failure before considering the
160 /// handler's answer. All temporary prefix storage drops on refusal.
161 pub fn finish<P>(
162 self,
163 answer: Result<Completeness, VisitError<P, DependencyRefused>>,
164 ) -> Result<DependencyReport, DependencyError<N, E, P>> {
165 if let Some(cause) = self.first {
166 return Err(match cause {
167 DependencyError::Processing { node, cause } => {
168 DependencyError::Processing { node, cause }
169 }
170 DependencyError::Resource { node, cause } => {
171 DependencyError::Resource { node, cause }
172 }
173 DependencyError::CallbackProtocol { node } => {
174 DependencyError::CallbackProtocol { node }
175 }
176 DependencyError::Provider { cause, .. } => match cause {},
177 });
178 }
179 let completeness = match answer {
180 Ok(value) => value,
181 Err(VisitError::Processing(cause)) => {
182 return Err(DependencyError::Provider {
183 node: self.node,
184 cause,
185 });
186 }
187 Err(VisitError::Visitor(DependencyRefused)) => {
188 return Err(DependencyError::CallbackProtocol { node: self.node });
189 }
190 };
191 Ok(DependencyReport {
192 completeness,
193 findings: self.findings.into_owned(),
194 })
195 }
196}