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    fn of(content: &Octets<'_>, parent: &Reader<'_>, field: &'static str) -> Result<Self> {
63        let ranges = if let [piece] = content.pieces() {
64            Ranges::One(piece.span())
65        } else {
66            let execution = parent.execution();
67            let refusal = |reason| Error::resource(reason, field, content.span().start);
68            execution
69                .charge_work(content.pieces().len() as u64)
70                .map_err(refusal)?;
71            let mut ranges = execution.vector();
72            ranges
73                .try_reserve_exact(content.pieces().len())
74                .map_err(refusal)?;
75            for piece in content.pieces() {
76                ranges.try_push(piece.span()).map_err(refusal)?;
77            }
78            Ranges::Many(ranges.into_owned())
79        };
80        Ok(Self {
81            ranges,
82            carrier: content.span(),
83            length: content.len() as u64,
84            empty_end: content.content_span().end(),
85            field,
86        })
87    }
88
89    fn mapped(
90        content: &Octets<'_>,
91        parent: &Reader<'_>,
92        field: &'static str,
93        origin: &Self,
94    ) -> Result<Self> {
95        let work = parent.execution();
96        let start = origin.project_offset(content.span().start, &work)?;
97        let end = origin.project_offset(content.span().end(), &work)?;
98        let empty_end = origin.project_offset(content.content_span().end(), &work)?;
99        if origin.coordinates() == Coordinates::Original {
100            // These already are physical positions in one original range.
101            // Validate through the supplied context without allocating a map.
102            return Self::of(content, parent, field);
103        }
104        let carrier = Span {
105            start,
106            length: end - start,
107        };
108        let refusal = |reason| Error::resource(reason, field, carrier.start);
109        work.charge_work(content.pieces().len() as u64)
110            .map_err(refusal)?;
111        let mut ranges = work.vector();
112        for piece in content.pieces() {
113            origin.append_projection(piece.span(), &work, &mut ranges)?;
114        }
115        Ok(Self {
116            ranges: Ranges::Many(ranges.into_owned()),
117            carrier,
118            length: content.len() as u64,
119            empty_end,
120            field,
121        })
122    }
123
124    /// Whether located errors and element spans are physical or logical.
125    #[must_use]
126    pub fn coordinates(&self) -> Coordinates {
127        match self.ranges {
128            Ranges::One(_) => Coordinates::Original,
129            Ranges::Many(_) => Coordinates::Logical,
130        }
131    }
132
133    /// Honest physical enclosing range, not a claim of contiguous payload.
134    #[must_use]
135    pub fn carrier(&self) -> Span {
136        self.carrier
137    }
138
139    fn ranges(&self) -> &[Span] {
140        match &self.ranges {
141            Ranges::One(span) => core::slice::from_ref(span),
142            Ranges::Many(spans) => spans,
143        }
144    }
145
146    fn logical_offset(&self, offset: u64) -> Result<u64> {
147        match self.ranges {
148            Ranges::One(span) if offset >= span.start && offset <= span.end() => {
149                Ok(offset - span.start)
150            }
151            Ranges::Many(_) if offset <= self.length => Ok(offset),
152            _ => Err(self.invalid()),
153        }
154    }
155
156    fn invalid(&self) -> Error {
157        Error::schema(ErrorCode::InvalidLocation, self.field, self.carrier.start)
158    }
159
160    fn admit(&self, execution: &impl Work) -> Result<()> {
161        execution
162            .charge_work(self.ranges().len() as u64)
163            .map_err(|reason| Error::resource(reason, self.field, self.carrier.start))
164    }
165
166    fn point(&self, logical: u64) -> u64 {
167        let mut base = 0;
168        for span in self.ranges() {
169            if logical < base + span.length {
170                return span.start + logical - base;
171            }
172            base += span.length;
173        }
174        self.ranges()
175            .last()
176            .map_or(self.empty_end, |span| span.end())
177    }
178
179    /// Maps a reader offset to one original physical point.
180    ///
181    /// A fragment boundary belongs to the next nonempty fragment. EOF maps to
182    /// the end of the last fragment, including a trailing empty fragment; an
183    /// empty constructed value maps to its original contents end, before EOC.
184    /// An out-of-window offset is terminal caller misuse, not document syntax.
185    pub fn project_offset(&self, offset: u64, execution: &impl Work) -> Result<u64> {
186        let logical = self.logical_offset(offset)?;
187        self.admit(execution)?;
188        Ok(self.point(logical))
189    }
190
191    /// Maps a reader range to its actual disjoint physical ranges.
192    ///
193    /// The returned capacity is accounted independently. No contiguous span is
194    /// invented across fragment headers. A zero-length range yields one actual
195    /// boundary point. Invalid endpoints are refused before work or allocation.
196    pub fn project_span(&self, span: Span, execution: &impl Work) -> Result<OwnedVec<Span>> {
197        let mut output = execution.vector();
198        self.append_projection(span, execution, &mut output)?;
199        Ok(output.into_owned())
200    }
201
202    fn append_projection<W: Work>(
203        &self,
204        span: Span,
205        execution: &W,
206        output: &mut OwnedVec<Span, W>,
207    ) -> Result<()> {
208        let end = span
209            .start
210            .checked_add(span.length)
211            .ok_or_else(|| self.invalid())?;
212        let start = self.logical_offset(span.start)?;
213        let end = self.logical_offset(end)?;
214        self.admit(execution)?;
215        let refusal = |reason| Error::resource(reason, self.field, self.carrier.start);
216        if start == end {
217            output
218                .try_push(Span {
219                    start: self.point(start),
220                    length: 0,
221                })
222                .map_err(refusal)?;
223        } else {
224            let mut base = 0;
225            for original in self.ranges() {
226                let next = base + original.length;
227                let low = start.max(base);
228                let high = end.min(next);
229                if low < high {
230                    output
231                        .try_push(Span {
232                            start: original.start + low - base,
233                            length: high - low,
234                        })
235                        .map_err(refusal)?;
236                }
237                base = next;
238            }
239        }
240        Ok(())
241    }
242}
243
244/// Failure of a scoped carried-content operation.
245#[derive(Debug)]
246pub enum ContentError<E> {
247    /// Admission or reader creation failed at the physical enclosing source.
248    Processing(Error),
249    /// Reader creation failed in the explicitly identified source coordinates.
250    Decoding {
251        /// Original reader failure, with its diagnostic storage unchanged.
252        source: Error,
253        /// Retained mapping identifying the reader's source coordinates.
254        context: SourceMap,
255    },
256    /// The callback's opaque cause and its independently retained source context.
257    ///
258    /// The cause is not rewritten. Offsets produced by the supplied reader use
259    /// `context.coordinates()`; callers can project their known error types
260    /// inside the callback or afterwards through this retained context. Arbitrary
261    /// callback errors need not have an inner location; `context.carrier()` is
262    /// their honest physical enclosure. Projection refusal remains a separate
263    /// terminal error and must not replace the original cause.
264    Callback {
265        /// The caller's unchanged error cause.
266        source: E,
267        /// Retained mapping for locations derived from the supplied reader.
268        context: SourceMap,
269    },
270}
271
272impl Octets<'_> {
273    /// Decodes carried bytes without letting a temporary joined buffer escape.
274    ///
275    /// One fragment is borrowed; several are explicitly materialized. The
276    /// original parser, carrying depth, cumulative local ceiling and current
277    /// aggregate execution survive. `R` and `E` cannot borrow the scoped reader,
278    /// model or joined bytes. Owned outputs must admit their own retained copies.
279    /// Mapping metadata and joins are admitted before invoking the callback.
280    /// The content must use original document coordinates. For content decoded
281    /// inside a scoped mapped reader, use [`Self::with_nested_reader_mapped`].
282    #[expect(
283        clippy::result_large_err,
284        reason = "the failure owns both the original cause and its source map; boxing would require another admitted allocation on the refusal path"
285    )]
286    pub fn with_nested_reader<R, E>(
287        &self,
288        parent: &Reader<'_>,
289        encoding: Encoding,
290        field: &'static str,
291        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
292    ) -> core::result::Result<R, ContentError<E>> {
293        self.nested(parent, encoding, field, None, callback)
294    }
295
296    /// Decodes content inside an existing source context, composing provenance.
297    ///
298    /// Supply the context that produced this content. Parent logical ranges are
299    /// flattened to actual physical ranges before any callback; retained errors
300    /// borrow neither the parent map nor its temporary buffer. Even a borrowed
301    /// single piece can have disjoint physical provenance. The carrier is a
302    /// physical enclosure including gaps, not a fabricated contiguous encoding.
303    #[expect(
304        clippy::result_large_err,
305        reason = "retain original cause and source context without another allocation on refusal"
306    )]
307    pub fn with_nested_reader_mapped<R, E>(
308        &self,
309        parent: &Reader<'_>,
310        source: &SourceMap,
311        encoding: Encoding,
312        field: &'static str,
313        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
314    ) -> core::result::Result<R, ContentError<E>> {
315        self.nested(parent, encoding, field, Some(source), callback)
316    }
317
318    #[expect(
319        clippy::result_large_err,
320        reason = "the owned source context must accompany its unchanged original cause"
321    )]
322    fn nested<R, E>(
323        &self,
324        parent: &Reader<'_>,
325        encoding: Encoding,
326        field: &'static str,
327        origin: Option<&SourceMap>,
328        callback: impl for<'scope> FnOnce(&mut Reader<'scope>, &SourceMap) -> core::result::Result<R, E>,
329    ) -> core::result::Result<R, ContentError<E>> {
330        if !core::ptr::eq(self.parser(), parent.parser()) {
331            // Refuse before traversing the map. A mapped input has an honest
332            // physical enclosure available in O(1), not a physical child offset.
333            let at = origin.map_or(self.span().start, |source| source.carrier().start);
334            return Err(ContentError::Processing(Error::pkix(
335                structured_pkix::Error::new(structured_pkix::ErrorCode::ForeignReader, field, at),
336            )));
337        }
338        let context = match origin {
339            Some(origin) => SourceMap::mapped(self, parent, field, origin),
340            None => SourceMap::of(self, parent, field),
341        }
342        .map_err(ContentError::Processing)?;
343        let joined;
344        let (bytes, base) = if let [piece] = self.pieces() {
345            let base = if context.coordinates() == Coordinates::Original {
346                piece.span().start
347            } else {
348                0
349            };
350            (piece.as_bytes(), base)
351        } else {
352            joined = self
353                .copy_in(parent, field, context.carrier().start)
354                .map_err(ContentError::Processing)?;
355            (&*joined, 0)
356        };
357        let reader =
358            self.parser()
359                .read_nested_query_in(parent, bytes, base, encoding, self.outer_depth());
360        let mut reader = match reader {
361            Ok(reader) => reader,
362            Err(source) => {
363                return Err(ContentError::Decoding {
364                    source: Error::encoding(source, field),
365                    context,
366                });
367            }
368        };
369        match callback(&mut reader, &context) {
370            Ok(output) => Ok(output),
371            Err(source) => Err(ContentError::Callback { source, context }),
372        }
373    }
374}