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 {}