Skip to main content

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}