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
276impl Octets<'_> {
277    /// Decodes carried bytes without letting a temporary joined buffer escape.
278    ///
279    /// One fragment is borrowed; several are explicitly materialized. The
280    /// original parser, carrying depth, cumulative local ceiling and current
281    /// aggregate execution survive. `R` and `E` cannot borrow the scoped reader,
282    /// model or joined bytes. Owned outputs must admit their own retained copies.
283    /// Mapping metadata and joins are admitted before invoking the callback.
284    /// The content must use original document coordinates. For content decoded
285    /// inside a scoped mapped reader, use [`Self::with_nested_reader_mapped`].
286    #[expect(
287        clippy::result_large_err,
288        reason = "the failure owns both the original cause and its source map; boxing would require another admitted allocation on the refusal path"
289    )]
290    pub fn with_nested_reader<R, E>(
291        &self,
292        parent: &Reader<'_>,
293        encoding: Encoding,
294        field: &'static str,
295        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
296    ) -> core::result::Result<R, ContentError<E>> {
297        self.nested(parent, encoding, field, None, callback)
298    }
299
300    /// Decodes content inside an existing source context, composing provenance.
301    ///
302    /// Supply the context that produced this content. Parent logical ranges are
303    /// flattened to actual physical ranges before any callback; retained errors
304    /// borrow neither the parent map nor its temporary buffer. Even a borrowed
305    /// single piece can have disjoint physical provenance. The carrier is a
306    /// physical enclosure including gaps, not a fabricated contiguous encoding.
307    #[expect(
308        clippy::result_large_err,
309        reason = "retain original cause and source context without another allocation on refusal"
310    )]
311    pub fn with_nested_reader_mapped<R, E>(
312        &self,
313        parent: &Reader<'_>,
314        source: &SourceMap,
315        encoding: Encoding,
316        field: &'static str,
317        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
318    ) -> core::result::Result<R, ContentError<E>> {
319        self.nested(parent, encoding, field, Some(source), callback)
320    }
321
322    #[expect(
323        clippy::result_large_err,
324        reason = "the owned source context must accompany its unchanged original cause"
325    )]
326    fn nested<R, E>(
327        &self,
328        parent: &Reader<'_>,
329        encoding: Encoding,
330        field: &'static str,
331        origin: Option<&SourceMap>,
332        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
333    ) -> core::result::Result<R, ContentError<E>> {
334        if !core::ptr::eq(self.parser(), parent.parser()) {
335            // Refuse before traversing the map. A mapped input has an honest
336            // physical enclosure available in O(1), not a physical child offset.
337            let at = origin.map_or(self.span().start, |source| source.carrier().start);
338            return Err(ContentError::Processing(Error::pkix(
339                structured_pkix::Error::new(structured_pkix::ErrorCode::ForeignReader, field, at),
340            )));
341        }
342        let context = match origin {
343            Some(origin) => SourceMap::mapped(self, parent, field, origin),
344            None => SourceMap::of(self, parent, field),
345        }
346        .map_err(ContentError::Processing)?;
347        let joined;
348        let (bytes, base) = if let [piece] = self.pieces() {
349            let base = if context.coordinates() == Coordinates::Original {
350                piece.span().start
351            } else {
352                0
353            };
354            (piece.as_bytes(), base)
355        } else {
356            joined = self
357                .copy_in(parent, field, context.carrier().start)
358                .map_err(ContentError::Processing)?;
359            (&*joined, 0)
360        };
361        let reader =
362            self.parser()
363                .read_nested_query_in(parent, bytes, base, encoding, self.outer_depth());
364        let mut reader = match reader {
365            Ok(reader) => reader,
366            Err(source) => {
367                return Err(ContentError::Decoding {
368                    source: Error::encoding(source, field),
369                    context,
370                });
371            }
372        };
373        match callback(&mut reader, &context) {
374            Ok(output) => Ok(output),
375            Err(source) => Err(ContentError::Callback { source, context }),
376        }
377    }
378}