Skip to main content

oxgraph_snapshot/
container_error.rs

1//! Concrete error types for snapshot reading, viewing, and writing.
2//!
3//! Each error type implements [`Display`](core::fmt::Display) and
4//! [`core::error::Error`] without depending on `alloc`, `std`, or external
5//! error frameworks.
6
7use core::fmt;
8
9/// Snapshot container validation error.
10///
11/// Returned by [`Snapshot::open`](crate::Snapshot::open) and
12/// [`Snapshot::open_with`](crate::Snapshot::open_with) for any header,
13/// section table, or layout-level invariant violation.
14///
15/// # Performance
16///
17/// `perf: unspecified`; errors are returned only from validation paths.
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub enum SnapshotError {
20    /// Snapshot bytes were shorter than the fixed header.
21    TruncatedHeader {
22        /// Bytes required for the fixed header.
23        needed: usize,
24        /// Bytes actually provided.
25        actual: usize,
26    },
27    /// Header bytes were present but could not be interpreted.
28    MalformedHeader,
29    /// Magic bytes did not match [`FORMAT_MAGIC`](crate::FORMAT_MAGIC).
30    BadMagic {
31        /// Actual magic bytes from the snapshot.
32        actual: [u8; 8],
33    },
34    /// Format major version did not equal the supported value.
35    FormatMajorMismatch {
36        /// Major version recorded in the snapshot.
37        actual: u32,
38        /// Major version this library supports.
39        supported: u32,
40    },
41    /// Format minor version was newer than this library can read.
42    FormatMinorTooNew {
43        /// Minor version recorded in the snapshot.
44        actual: u32,
45        /// Highest minor version this library accepts.
46        max_supported: u32,
47    },
48    /// Header `header_size` field did not match the expected value.
49    HeaderSizeMismatch {
50        /// `header_size` value recorded in the snapshot.
51        actual: u32,
52        /// Header size this library expects.
53        expected: u32,
54    },
55    /// Header reserved bytes were not all zero.
56    NonZeroHeaderReserved,
57    /// `section_count` exceeded the v1 cap.
58    SectionCountTooLarge {
59        /// Section count recorded in the snapshot.
60        count: u32,
61        /// Maximum permitted section count.
62        max: u32,
63    },
64    /// Bytes after the header were too short for the declared section table.
65    TruncatedSectionTable {
66        /// Bytes required for the declared section table.
67        needed: usize,
68        /// Bytes available after the header.
69        actual: usize,
70    },
71    /// Section table bytes could not be interpreted.
72    MalformedSectionTable,
73    /// A section entry's trailing reserved bytes were not all zero.
74    NonZeroEntryReserved {
75        /// Section kind whose entry violated the invariant.
76        kind: u32,
77    },
78    /// A section entry declared an unsupported flags bit.
79    UnsupportedFlags {
80        /// Section kind whose entry violated the invariant.
81        kind: u32,
82        /// Flags byte recorded in the entry.
83        flags: u8,
84    },
85    /// A section entry declared an `alignment_log2` larger than permitted.
86    AlignmentLog2TooLarge {
87        /// Section kind whose entry violated the invariant.
88        kind: u32,
89        /// Declared `alignment_log2` value.
90        alignment_log2: u8,
91    },
92    /// `offset + length` overflowed `u64` for one entry.
93    SectionRangeOverflow {
94        /// Section kind whose entry violated the invariant.
95        kind: u32,
96    },
97    /// A section's byte range fell outside the snapshot.
98    SectionOutOfBounds {
99        /// Section kind whose entry violated the invariant.
100        kind: u32,
101        /// Declared byte offset.
102        offset: u64,
103        /// Declared byte length.
104        length: u64,
105        /// Total snapshot byte length.
106        snapshot_len: u64,
107    },
108    /// Section table entries were not in monotonic non-decreasing offset order
109    /// or one entry overlapped its predecessor.
110    UnsortedSectionTable {
111        /// Index of the entry whose offset violated monotonicity.
112        index: usize,
113    },
114    /// A section entry's kind was not strictly greater than its predecessor's.
115    ///
116    /// The container mandates a strictly-ascending kind order, which also makes the
117    /// table duplicate-free by construction.
118    NonAscendingKind {
119        /// Kind of the offending entry.
120        kind: u32,
121        /// Kind of the preceding entry.
122        prev: u32,
123    },
124    /// The header's `table_crc32c` did not match the section-table bytes.
125    TableChecksumMismatch {
126        /// Checksum recorded in the header.
127        expected: u32,
128        /// Checksum recomputed over the section-table bytes.
129        actual: u32,
130    },
131    /// A section payload's checksum did not match its table entry.
132    SectionChecksumMismatch {
133        /// Kind of the failing section.
134        kind: u32,
135        /// Checksum recorded in the section entry.
136        expected: u32,
137        /// Checksum recomputed over the payload bytes.
138        actual: u32,
139    },
140    /// No section with the requested kind was present.
141    SectionMissing {
142        /// Requested section kind.
143        kind: u32,
144    },
145    /// A `u64` value could not be represented as `usize` on this target.
146    UsizeOverflow {
147        /// Value that could not be represented as `usize`.
148        value: u64,
149    },
150}
151
152impl SnapshotError {
153    /// Formats the header-level variants; returns `None` for the rest.
154    ///
155    /// Split out of [`fmt::Display`] purely to keep each formatting function
156    /// within the complexity budget.
157    fn fmt_header_issue(&self, formatter: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
158        match self {
159            Self::TruncatedHeader { needed, actual } => Some(write!(
160                formatter,
161                "snapshot header is truncated: needed {needed} bytes, got {actual}"
162            )),
163            Self::MalformedHeader => Some(formatter.write_str("snapshot header is malformed")),
164            Self::BadMagic { actual } => Some(write!(formatter, "bad snapshot magic: {actual:?}")),
165            Self::FormatMajorMismatch { actual, supported } => Some(write!(
166                formatter,
167                "unsupported snapshot format major: snapshot is {actual}, this reader supports {supported}"
168            )),
169            Self::FormatMinorTooNew {
170                actual,
171                max_supported,
172            } => Some(write!(
173                formatter,
174                "snapshot format minor {actual} is newer than this reader's maximum {max_supported}"
175            )),
176            Self::HeaderSizeMismatch { actual, expected } => Some(write!(
177                formatter,
178                "header_size mismatch: snapshot reports {actual}, this reader expects {expected}"
179            )),
180            Self::NonZeroHeaderReserved => {
181                Some(formatter.write_str("snapshot header reserved bytes are not all zero"))
182            }
183            Self::SectionCountTooLarge { count, max } => Some(write!(
184                formatter,
185                "section count {count} exceeds maximum {max}"
186            )),
187            Self::TruncatedSectionTable { needed, actual } => Some(write!(
188                formatter,
189                "section table is truncated: needed {needed} bytes, got {actual}"
190            )),
191            Self::MalformedSectionTable => {
192                Some(formatter.write_str("section table bytes are malformed"))
193            }
194            _ => None,
195        }
196    }
197}
198
199impl fmt::Display for SnapshotError {
200    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
201        if let Some(result) = self.fmt_header_issue(formatter) {
202            return result;
203        }
204        match self {
205            Self::NonZeroEntryReserved { kind } => write!(
206                formatter,
207                "section {kind} entry trailing reserved bytes are not all zero"
208            ),
209            Self::UnsupportedFlags { kind, flags } => write!(
210                formatter,
211                "section {kind} entry has unsupported flags byte {flags:#04x}"
212            ),
213            Self::AlignmentLog2TooLarge {
214                kind,
215                alignment_log2,
216            } => write!(
217                formatter,
218                "section {kind} alignment_log2 {alignment_log2} exceeds maximum"
219            ),
220            Self::SectionRangeOverflow { kind } => {
221                write!(formatter, "section {kind} offset + length overflows u64")
222            }
223            Self::SectionOutOfBounds {
224                kind,
225                offset,
226                length,
227                snapshot_len,
228            } => write!(
229                formatter,
230                "section {kind} is out of bounds: offset {offset}, length {length}, snapshot length {snapshot_len}"
231            ),
232            Self::UnsortedSectionTable { index } => write!(
233                formatter,
234                "section table entry at index {index} is unsorted or overlaps its predecessor"
235            ),
236            Self::NonAscendingKind { kind, prev } => write!(
237                formatter,
238                "section kind {kind} is not strictly greater than its predecessor {prev}"
239            ),
240            Self::TableChecksumMismatch { expected, actual } => write!(
241                formatter,
242                "section table checksum mismatch: header records {expected:#010x}, table bytes hash to {actual:#010x}"
243            ),
244            Self::SectionChecksumMismatch {
245                kind,
246                expected,
247                actual,
248            } => write!(
249                formatter,
250                "section {kind} payload checksum mismatch: entry records {expected:#010x}, payload hashes to {actual:#010x}"
251            ),
252            Self::SectionMissing { kind } => {
253                write!(formatter, "snapshot section {kind} is missing")
254            }
255            Self::UsizeOverflow { value } => {
256                write!(formatter, "u64 value {value} does not fit usize")
257            }
258            // Already formatted by `fmt_header_issue`; the early return above
259            // makes these arms unreachable, and listing them explicitly keeps
260            // the match exhaustive when a variant is added.
261            Self::TruncatedHeader { .. }
262            | Self::MalformedHeader
263            | Self::BadMagic { .. }
264            | Self::FormatMajorMismatch { .. }
265            | Self::FormatMinorTooNew { .. }
266            | Self::HeaderSizeMismatch { .. }
267            | Self::NonZeroHeaderReserved
268            | Self::SectionCountTooLarge { .. }
269            | Self::TruncatedSectionTable { .. }
270            | Self::MalformedSectionTable => Ok(()),
271        }
272    }
273}
274
275impl core::error::Error for SnapshotError {}
276
277/// Error returned when borrowing a section payload as a typed slice fails.
278///
279/// # Performance
280///
281/// `perf: unspecified`; errors are returned only from typed-view paths.
282#[derive(Clone, Copy, Debug, Eq, PartialEq)]
283pub enum SectionViewError {
284    /// The requested element type is zero-sized, so it cannot tile a payload.
285    ZeroSizedType,
286    /// Payload byte length is not an exact multiple of `size_of::<T>()`.
287    LengthNotMultipleOfSize {
288        /// Section payload length in bytes.
289        length: usize,
290        /// `core::mem::size_of::<T>()` for the requested element type.
291        elem_size: usize,
292    },
293    /// Payload base address does not satisfy `align_of::<T>()`.
294    AlignmentMismatch {
295        /// Address of the payload's first byte.
296        ptr_addr: usize,
297        /// `core::mem::align_of::<T>()` for the requested element type.
298        required: usize,
299    },
300    /// Payload checksum did not match the section entry's recorded value.
301    ChecksumMismatch {
302        /// Kind of the failing section.
303        kind: u32,
304        /// Checksum recorded in the section entry.
305        expected: u32,
306        /// Checksum recomputed over the payload bytes.
307        actual: u32,
308    },
309}
310
311impl fmt::Display for SectionViewError {
312    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
313        match self {
314            Self::ZeroSizedType => {
315                formatter.write_str("cannot borrow a section payload as a zero-sized type")
316            }
317            Self::LengthNotMultipleOfSize { length, elem_size } => write!(
318                formatter,
319                "section length {length} is not a multiple of element size {elem_size}"
320            ),
321            Self::AlignmentMismatch { ptr_addr, required } => write!(
322                formatter,
323                "section payload at address {ptr_addr:#x} is not aligned to {required}"
324            ),
325            Self::ChecksumMismatch {
326                kind,
327                expected,
328                actual,
329            } => write!(
330                formatter,
331                "section {kind} payload checksum mismatch: entry records {expected:#010x}, payload hashes to {actual:#010x}"
332            ),
333        }
334    }
335}
336
337impl core::error::Error for SectionViewError {}
338
339/// Error returned when binding a width-typed section by kind and version.
340///
341/// Returned by [`Snapshot::typed_section`](crate::Snapshot::typed_section): a
342/// single error covering the lookup, version check, and typed-view steps that
343/// every layout crate previously open-coded with its own variants.
344///
345/// # Performance
346///
347/// `perf: unspecified`; errors are returned only from section-binding paths.
348#[derive(Clone, Copy, Debug, Eq, PartialEq)]
349pub enum SectionBindError {
350    /// No section with the requested kind was present.
351    Missing {
352        /// Requested section kind.
353        kind: u32,
354    },
355    /// The section was present but its version did not match.
356    VersionMismatch {
357        /// Requested section kind.
358        kind: u32,
359        /// Version the caller required.
360        expected: u32,
361        /// Version recorded in the section entry.
362        actual: u32,
363    },
364    /// The payload could not be borrowed as the requested little-endian word.
365    View {
366        /// Requested section kind.
367        kind: u32,
368        /// Underlying typed-view failure.
369        error: SectionViewError,
370    },
371}
372
373impl fmt::Display for SectionBindError {
374    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
375        match self {
376            Self::Missing { kind } => write!(formatter, "snapshot section {kind} is missing"),
377            Self::VersionMismatch {
378                kind,
379                expected,
380                actual,
381            } => write!(
382                formatter,
383                "snapshot section {kind} version {actual} does not match expected {expected}"
384            ),
385            Self::View { kind, error } => {
386                write!(
387                    formatter,
388                    "snapshot section {kind} typed view failed: {error}"
389                )
390            }
391        }
392    }
393}
394
395impl core::error::Error for SectionBindError {}
396
397/// Error returned by snapshot writers.
398///
399/// Returned by [`SnapshotPlan::new`](crate::SnapshotPlan::new),
400/// [`SnapshotPlan::write_into`](crate::SnapshotPlan::write_into), and the
401/// alloc-gated [`SnapshotWriter`](crate::SnapshotWriter) methods.
402///
403/// # Performance
404///
405/// `perf: unspecified`; errors are returned only from writer paths.
406#[derive(Clone, Copy, Debug, Eq, PartialEq)]
407pub enum PlanError {
408    /// The output buffer is smaller than the snapshot's encoded length.
409    BufferTooSmall {
410        /// Bytes required by the encoded snapshot.
411        needed: usize,
412        /// Bytes provided in the output buffer.
413        actual: usize,
414    },
415    /// A section's `alignment_log2` exceeds [`MAX_ALIGNMENT_LOG2`](crate::MAX_ALIGNMENT_LOG2).
416    AlignmentTooLarge {
417        /// Declared `alignment_log2` value.
418        alignment_log2: u8,
419    },
420    /// Computed offsets or lengths overflowed `u64` or `usize`.
421    PayloadOverflow,
422    /// More sections were supplied than the format cap permits.
423    TooManySections {
424        /// Section count actually supplied.
425        count: usize,
426    },
427    /// A pending section's kind was not strictly greater than its
428    /// predecessor's.
429    ///
430    /// The container mandates strictly-ascending kind order in the section table, which
431    /// also makes it duplicate-free by construction.
432    NonAscendingKind {
433        /// Kind of the offending section.
434        kind: u32,
435        /// Kind of the preceding section.
436        prev: u32,
437    },
438}
439
440impl fmt::Display for PlanError {
441    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
442        match self {
443            Self::BufferTooSmall { needed, actual } => write!(
444                formatter,
445                "output buffer too small: needed {needed} bytes, got {actual}"
446            ),
447            Self::AlignmentTooLarge { alignment_log2 } => write!(
448                formatter,
449                "alignment_log2 {alignment_log2} exceeds the format maximum"
450            ),
451            Self::PayloadOverflow => {
452                formatter.write_str("snapshot payload arithmetic overflowed u64 or usize")
453            }
454            Self::TooManySections { count } => {
455                write!(
456                    formatter,
457                    "section count {count} exceeds the format maximum"
458                )
459            }
460            Self::NonAscendingKind { kind, prev } => write!(
461                formatter,
462                "section kind {kind} is not strictly greater than its predecessor {prev}"
463            ),
464        }
465    }
466}
467
468impl core::error::Error for PlanError {}