Skip to main content

structured_cms/
content_source.rs

1//! Scoped decoding of carried encodings with explicit physical provenance.
2
3use crate::{Error, ErrorCode, Octets, Result};
4use structured_asn1::memory::{OwnedVec, Work};
5use structured_asn1::{Encoding, Reader, Span};
6
7/// Coordinate system used by the scoped reader and its located diagnostics.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Coordinates {
10    /// Original document offsets; a single fragment is borrowed directly.
11    Original,
12    /// Offsets into joined content starting at zero, excluding fragment headers.
13    Logical,
14}
15
16#[derive(Debug)]
17enum Ranges {
18    One(Span),
19    Many(OwnedVec<Span>),
20}
21
22/// Independently retained mapping from a scoped reader to the original bytes.
23///
24/// This owns only range metadata, never the temporary joined bytes. Keeping it
25/// with a callback error preserves provenance after that temporary buffer and
26/// its parser-bound view disappear. Projection uses the current query's work;
27/// retained capacity remains charged to the original account.
28#[derive(Debug)]
29pub struct SourceMap {
30    ranges: Ranges,
31    carrier: Span,
32    length: u64,
33    empty_end: u64,
34    field: &'static str,
35}
36
37impl SourceMap {
38    /// Copies source metadata for an independently retained result/error.
39    /// Capacity and copying are admitted by current work; neither source bytes
40    /// nor parser/query references are kept. Original/Logical coordinates and
41    /// physical fragment boundaries remain unchanged after either parser drops.
42    pub fn try_copy_in<W: Work>(&self, work: &W) -> Result<Self> {
43        let fail = |reason| Error::resource(reason, self.field, self.carrier.start);
44        work.charge_work(1).map_err(fail)?;
45        let ranges = match &self.ranges {
46            Ranges::One(span) => Ranges::One(*span),
47            Ranges::Many(spans) => {
48                let mut copy = work.vector();
49                copy.try_extend_from_slice(spans).map_err(fail)?;
50                Ranges::Many(copy.into_owned())
51            }
52        };
53        Ok(Self {
54            ranges,
55            carrier: self.carrier,
56            length: self.length,
57            empty_end: self.empty_end,
58            field: self.field,
59        })
60    }
61
62    pub(crate) fn of(
63        content: &Octets<'_>,
64        parent: &Reader<'_>,
65        field: &'static str,
66    ) -> Result<Self> {
67        let ranges = if let [piece] = content.pieces() {
68            Ranges::One(piece.span())
69        } else {
70            let execution = parent.execution();
71            let refusal = |reason| Error::resource(reason, field, content.span().start);
72            execution
73                .charge_work(content.pieces().len() as u64)
74                .map_err(refusal)?;
75            let mut ranges = execution.vector();
76            ranges
77                .try_reserve_exact(content.pieces().len())
78                .map_err(refusal)?;
79            for piece in content.pieces() {
80                ranges.try_push(piece.span()).map_err(refusal)?;
81            }
82            Ranges::Many(ranges.into_owned())
83        };
84        Ok(Self {
85            ranges,
86            carrier: content.span(),
87            length: content.len() as u64,
88            empty_end: content.content_span().end(),
89            field,
90        })
91    }
92
93    fn mapped(
94        content: &Octets<'_>,
95        parent: &Reader<'_>,
96        field: &'static str,
97        origin: &Self,
98    ) -> Result<Self> {
99        let work = parent.execution();
100        let start = origin.project_offset(content.span().start, &work)?;
101        let end = origin.project_offset(content.span().end(), &work)?;
102        let empty_end = origin.project_offset(content.content_span().end(), &work)?;
103        if origin.coordinates() == Coordinates::Original {
104            // These already are physical positions in one original range.
105            // Validate through the supplied context without allocating a map.
106            return Self::of(content, parent, field);
107        }
108        let carrier = Span {
109            start,
110            length: end - start,
111        };
112        let refusal = |reason| Error::resource(reason, field, carrier.start);
113        work.charge_work(content.pieces().len() as u64)
114            .map_err(refusal)?;
115        let mut ranges = work.vector();
116        for piece in content.pieces() {
117            origin.append_projection(piece.span(), &work, &mut ranges)?;
118        }
119        Ok(Self {
120            ranges: Ranges::Many(ranges.into_owned()),
121            carrier,
122            length: content.len() as u64,
123            empty_end,
124            field,
125        })
126    }
127
128    /// Whether located errors and element spans are physical or logical.
129    #[must_use]
130    pub fn coordinates(&self) -> Coordinates {
131        match self.ranges {
132            Ranges::One(_) => Coordinates::Original,
133            Ranges::Many(_) => Coordinates::Logical,
134        }
135    }
136
137    /// Honest physical enclosing range, not a claim of contiguous payload.
138    #[must_use]
139    pub fn carrier(&self) -> Span {
140        self.carrier
141    }
142
143    fn ranges(&self) -> &[Span] {
144        match &self.ranges {
145            Ranges::One(span) => core::slice::from_ref(span),
146            Ranges::Many(spans) => spans,
147        }
148    }
149
150    fn logical_offset(&self, offset: u64) -> Result<u64> {
151        match self.ranges {
152            Ranges::One(span) if offset >= span.start && offset <= span.end() => {
153                Ok(offset - span.start)
154            }
155            Ranges::Many(_) if offset <= self.length => Ok(offset),
156            _ => Err(self.invalid()),
157        }
158    }
159
160    fn invalid(&self) -> Error {
161        Error::schema(ErrorCode::InvalidLocation, self.field, self.carrier.start)
162    }
163
164    fn admit(&self, execution: &impl Work) -> Result<()> {
165        execution
166            .charge_work(self.ranges().len() as u64)
167            .map_err(|reason| Error::resource(reason, self.field, self.carrier.start))
168    }
169
170    fn point(&self, logical: u64) -> u64 {
171        let mut base = 0;
172        for span in self.ranges() {
173            if logical < base + span.length {
174                return span.start + logical - base;
175            }
176            base += span.length;
177        }
178        self.ranges()
179            .last()
180            .map_or(self.empty_end, |span| span.end())
181    }
182
183    /// Maps a reader offset to one original physical point.
184    ///
185    /// A fragment boundary belongs to the next nonempty fragment. EOF maps to
186    /// the end of the last fragment, including a trailing empty fragment; an
187    /// empty constructed value maps to its original contents end, before EOC.
188    /// An out-of-window offset is terminal caller misuse, not document syntax.
189    pub fn project_offset(&self, offset: u64, execution: &impl Work) -> Result<u64> {
190        let logical = self.logical_offset(offset)?;
191        self.admit(execution)?;
192        Ok(self.point(logical))
193    }
194
195    /// Maps a reader range to its actual disjoint physical ranges.
196    ///
197    /// The returned capacity is accounted independently. No contiguous span is
198    /// invented across fragment headers. A zero-length range yields one actual
199    /// boundary point. Invalid endpoints are refused before work or allocation.
200    pub fn project_span(&self, span: Span, execution: &impl Work) -> Result<OwnedVec<Span>> {
201        let mut output = execution.vector();
202        self.append_projection(span, execution, &mut output)?;
203        Ok(output.into_owned())
204    }
205
206    fn append_projection<W: Work>(
207        &self,
208        span: Span,
209        execution: &W,
210        output: &mut OwnedVec<Span, W>,
211    ) -> Result<()> {
212        let end = span
213            .start
214            .checked_add(span.length)
215            .ok_or_else(|| self.invalid())?;
216        let start = self.logical_offset(span.start)?;
217        let end = self.logical_offset(end)?;
218        self.admit(execution)?;
219        let refusal = |reason| Error::resource(reason, self.field, self.carrier.start);
220        if start == end {
221            output
222                .try_push(Span {
223                    start: self.point(start),
224                    length: 0,
225                })
226                .map_err(refusal)?;
227        } else {
228            let mut base = 0;
229            for original in self.ranges() {
230                let next = base + original.length;
231                let low = start.max(base);
232                let high = end.min(next);
233                if low < high {
234                    output
235                        .try_push(Span {
236                            start: original.start + low - base,
237                            length: high - low,
238                        })
239                        .map_err(refusal)?;
240                }
241                base = next;
242            }
243        }
244        Ok(())
245    }
246}
247
248/// Failure of a scoped carried-content operation.
249#[derive(Debug)]
250pub enum ContentError<E> {
251    /// Admission or reader creation failed at the physical enclosing source.
252    Processing(Error),
253    /// Reader creation failed in the explicitly identified source coordinates.
254    Decoding {
255        /// Original reader failure, with its diagnostic storage unchanged.
256        source: Error,
257        /// Retained mapping identifying the reader's source coordinates.
258        context: SourceMap,
259    },
260    /// The callback's opaque cause and its independently retained source context.
261    ///
262    /// The cause is not rewritten. Offsets produced by the supplied reader use
263    /// `context.coordinates()`; callers can project their known error types
264    /// inside the callback or afterwards through this retained context. Arbitrary
265    /// callback errors need not have an inner location; `context.carrier()` is
266    /// their honest physical enclosure. Projection refusal remains a separate
267    /// terminal error and must not replace the original cause.
268    Callback {
269        /// The caller's unchanged error cause.
270        source: E,
271        /// Retained mapping for locations derived from the supplied reader.
272        context: SourceMap,
273    },
274}
275
276/// Scoped carried decoding that can await caller-owned external input without
277/// allowing temporary joined bytes or their reader to escape in the result.
278pub trait AsyncVisitor {
279    /// Independently retained result, not borrowed from a temporary join.
280    type Output;
281    /// Original source-independent callback cause.
282    type Error;
283
284    /// Executes inside the actual nested reader/source-map lifetime.
285    fn visit<'data: 'scope, 'scope>(
286        &'scope mut self,
287        reader: &'scope mut Reader<'data>,
288        context: &'scope SourceMap,
289    ) -> impl core::future::Future<Output = core::result::Result<Self::Output, Self::Error>>;
290}
291
292enum Carried<'a> {
293    Borrowed(&'a [u8]),
294    Joined(OwnedVec<u8>),
295}
296
297struct Prepared<'a> {
298    carried: Carried<'a>,
299    context: SourceMap,
300    base: u64,
301}
302
303impl Prepared<'_> {
304    fn bytes(&self) -> &[u8] {
305        match &self.carried {
306            Carried::Borrowed(bytes) => bytes,
307            Carried::Joined(bytes) => bytes,
308        }
309    }
310}
311
312impl Octets<'_> {
313    /// Awaits a callback while retaining the same accounted join, producing
314    /// source, inherited depth and current operation as resident decoding.
315    #[expect(
316        clippy::result_large_err,
317        reason = "retained source mapping preserves errors after the awaited join is dropped without allocating an error box"
318    )]
319    pub async fn with_nested_reader_async<V: AsyncVisitor>(
320        &self,
321        parent: &Reader<'_>,
322        origin: Option<&SourceMap>,
323        encoding: Encoding,
324        field: &'static str,
325        visitor: &mut V,
326    ) -> core::result::Result<V::Output, ContentError<V::Error>> {
327        let prepared = self
328            .prepare_nested(parent, field, origin)
329            .map_err(ContentError::Processing)?;
330        let reader = self.parser().read_nested_query_in(
331            parent,
332            prepared.bytes(),
333            prepared.base,
334            encoding,
335            self.outer_depth(),
336        );
337        let mut reader = match reader {
338            Ok(reader) => reader,
339            Err(cause) => {
340                return Err(ContentError::Decoding {
341                    source: Error::encoding(cause, field),
342                    context: prepared.context,
343                });
344            }
345        };
346        let result = visitor.visit(&mut reader, &prepared.context).await;
347        match result {
348            Ok(output) => Ok(output),
349            Err(source) => Err(ContentError::Callback {
350                source,
351                context: prepared.context,
352            }),
353        }
354    }
355
356    fn prepare_nested<'a>(
357        &'a self,
358        parent: &Reader<'_>,
359        field: &'static str,
360        origin: Option<&SourceMap>,
361    ) -> Result<Prepared<'a>> {
362        if !core::ptr::eq(self.parser(), parent.parser()) {
363            let at = origin.map_or(self.span().start, |source| source.carrier().start);
364            return Err(Error::pkix(structured_pkix::Error::new(
365                structured_pkix::ErrorCode::ForeignReader,
366                field,
367                at,
368            )));
369        }
370        let context = match origin {
371            Some(origin) => SourceMap::mapped(self, parent, field, origin),
372            None => SourceMap::of(self, parent, field),
373        }?;
374        let (carried, base) = if let [piece] = self.pieces() {
375            let base = if context.coordinates() == Coordinates::Original {
376                piece.span().start
377            } else {
378                0
379            };
380            (Carried::Borrowed(piece.as_bytes()), base)
381        } else {
382            (
383                Carried::Joined(self.copy_in(parent, field, context.carrier().start)?),
384                0,
385            )
386        };
387        Ok(Prepared {
388            carried,
389            context,
390            base,
391        })
392    }
393    /// Decodes carried bytes without letting a temporary joined buffer escape.
394    ///
395    /// One fragment is borrowed; several are explicitly materialized. The
396    /// original parser, carrying depth, cumulative local ceiling and current
397    /// aggregate execution survive. `R` and `E` cannot borrow the scoped reader,
398    /// model or joined bytes. Owned outputs must admit their own retained copies.
399    /// Mapping metadata and joins are admitted before invoking the callback.
400    /// The content must use original document coordinates. For content decoded
401    /// inside a scoped mapped reader, use [`Self::with_nested_reader_mapped`].
402    #[expect(
403        clippy::result_large_err,
404        reason = "the failure owns both the original cause and its source map; boxing would require another admitted allocation on the refusal path"
405    )]
406    pub fn with_nested_reader<R, E>(
407        &self,
408        parent: &Reader<'_>,
409        encoding: Encoding,
410        field: &'static str,
411        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
412    ) -> core::result::Result<R, ContentError<E>> {
413        self.nested(parent, encoding, field, None, callback)
414    }
415
416    /// Decodes content inside an existing source context, composing provenance.
417    ///
418    /// Supply the context that produced this content. Parent logical ranges are
419    /// flattened to actual physical ranges before any callback; retained errors
420    /// borrow neither the parent map nor its temporary buffer. Even a borrowed
421    /// single piece can have disjoint physical provenance. The carrier is a
422    /// physical enclosure including gaps, not a fabricated contiguous encoding.
423    #[expect(
424        clippy::result_large_err,
425        reason = "retain original cause and source context without another allocation on refusal"
426    )]
427    pub fn with_nested_reader_mapped<R, E>(
428        &self,
429        parent: &Reader<'_>,
430        source: &SourceMap,
431        encoding: Encoding,
432        field: &'static str,
433        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
434    ) -> core::result::Result<R, ContentError<E>> {
435        self.nested(parent, encoding, field, Some(source), callback)
436    }
437
438    #[expect(
439        clippy::result_large_err,
440        reason = "the owned source context must accompany its unchanged original cause"
441    )]
442    fn nested<R, E>(
443        &self,
444        parent: &Reader<'_>,
445        encoding: Encoding,
446        field: &'static str,
447        origin: Option<&SourceMap>,
448        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
449    ) -> core::result::Result<R, ContentError<E>> {
450        let prepared = self
451            .prepare_nested(parent, field, origin)
452            .map_err(ContentError::Processing)?;
453        let reader = self.parser().read_nested_query_in(
454            parent,
455            prepared.bytes(),
456            prepared.base,
457            encoding,
458            self.outer_depth(),
459        );
460        let mut reader = match reader {
461            Ok(reader) => reader,
462            Err(source) => {
463                return Err(ContentError::Decoding {
464                    source: Error::encoding(source, field),
465                    context: prepared.context,
466                });
467            }
468        };
469        match callback(&mut reader, &prepared.context) {
470            Ok(output) => Ok(output),
471            Err(source) => Err(ContentError::Callback {
472                source,
473                context: prepared.context,
474            }),
475        }
476    }
477}