rapidgzip-core 0.2.1

Parallel gzip, zlib, and raw-DEFLATE decoder using rapidgzip's marker/window algorithm
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
use crate::{DeflateIndex, Format, IndexError, IndexKind};
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::io;
use std::sync::Arc;

/// An analysis-owned collection or byte budget.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum AnalysisResource {
    /// Parsed container streams (gzip members, or one zlib/raw stream).
    Streams,
    /// Parsed DEFLATE blocks.
    Blocks,
    /// Retained optional gzip-header bytes.
    HeaderBytes,
    /// Retained Huffman code lengths.
    AlphabetCodeLengths,
    /// Retained individual back-reference records.
    Backreferences,
}

impl Display for AnalysisResource {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::Streams => "streams",
            Self::Blocks => "DEFLATE blocks",
            Self::HeaderBytes => "gzip header bytes",
            Self::AlphabetCodeLengths => "Huffman code lengths",
            Self::Backreferences => "back-reference records",
        })
    }
}

/// A checked counter maintained by structural analysis.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum AnalysisCounter {
    /// Absolute compressed bit positions and sizes.
    CompressedBits,
    /// Absolute decompressed byte positions and sizes.
    DecompressedBytes,
    /// Literal, copy, block, stream, or reference counts.
    StructuralItems,
}

impl Display for AnalysisCounter {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::CompressedBits => "compressed-bit",
            Self::DecompressedBytes => "decompressed-byte",
            Self::StructuralItems => "structural-item",
        })
    }
}

/// The reason structural analysis stopped without accepting the input.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum AnalysisErrorKind {
    /// A caller-configurable collection or metadata limit was reached.
    ResourceLimit {
        /// Limited resource.
        resource: AnalysisResource,
        /// Configured maximum number of items or bytes.
        limit: usize,
    },
    /// A bounded collection could not reserve memory.
    AllocationFailed {
        /// Collection whose allocation failed.
        resource: AnalysisResource,
        /// Additional items or bytes requested from the allocator.
        additional: usize,
    },
    /// An exact `u64` structural counter overflowed.
    CounterOverflow {
        /// Counter that could no longer represent the input.
        counter: AnalysisCounter,
    },
}

impl Display for AnalysisErrorKind {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::ResourceLimit { resource, limit } => {
                write!(
                    formatter,
                    "{resource} exceeded the configured limit of {limit}"
                )
            }
            Self::AllocationFailed {
                resource,
                additional,
            } => write!(
                formatter,
                "could not reserve space for {additional} additional {resource}"
            ),
            Self::CounterOverflow { counter } => {
                write!(formatter, "the {counter} analysis counter overflowed")
            }
        }
    }
}

/// The reason a gzip container was rejected.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum GzipErrorKind {
    /// The gzip identification bytes were absent.
    BadMagic,
    /// The member did not use the DEFLATE compression method.
    UnsupportedCompressionMethod(u8),
    /// One or more reserved flag bits were set.
    ReservedFlags(u8),
    /// The optional header checksum was incorrect.
    HeaderChecksumMismatch {
        /// Checksum stored in the header.
        expected: u16,
        /// Checksum computed over preceding header bytes.
        actual: u16,
    },
    /// A zero-terminated header field reached the end of input.
    UnterminatedHeaderField,
    /// The member header or footer was truncated.
    Truncated,
    /// Bytes after a valid member were not another gzip member.
    TrailingGarbage,
}

impl Display for GzipErrorKind {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::BadMagic => formatter.write_str("missing gzip magic bytes"),
            Self::UnsupportedCompressionMethod(method) => {
                write!(formatter, "unsupported gzip compression method {method}")
            }
            Self::ReservedFlags(flags) => {
                write!(formatter, "reserved gzip flag bits are set: {flags:#04x}")
            }
            Self::HeaderChecksumMismatch { expected, actual } => write!(
                formatter,
                "gzip header checksum mismatch: expected {expected:#06x}, got {actual:#06x}"
            ),
            Self::UnterminatedHeaderField => formatter.write_str("unterminated gzip header field"),
            Self::Truncated => formatter.write_str("truncated gzip header or footer"),
            Self::TrailingGarbage => formatter.write_str("trailing non-gzip data"),
        }
    }
}

/// The reason an RFC 1950 zlib container was rejected.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ZlibErrorKind {
    /// The header selected a compression method other than DEFLATE.
    UnsupportedCompressionMethod(u8),
    /// CINFO requested a window larger than DEFLATE's 32 KiB maximum.
    UnsupportedWindowSize(u8),
    /// The CMF/FLG pair did not satisfy the FCHECK residue.
    BadHeaderCheck,
    /// Preset dictionaries are not supported by this decoder.
    PresetDictionary,
    /// The zlib header or Adler-32 trailer was truncated.
    Truncated,
    /// The Adler-32 stored in the trailer did not match the output.
    ChecksumMismatch {
        /// Trailer value.
        expected: u32,
        /// Computed value.
        actual: u32,
    },
    /// Bytes remained after the one complete zlib stream.
    TrailingGarbage,
}

impl Display for ZlibErrorKind {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnsupportedCompressionMethod(method) => {
                write!(formatter, "unsupported zlib compression method {method}")
            }
            Self::UnsupportedWindowSize(cinfo) => {
                write!(formatter, "unsupported zlib CINFO window value {cinfo}")
            }
            Self::BadHeaderCheck => formatter.write_str("invalid zlib FCHECK header residue"),
            Self::PresetDictionary => {
                formatter.write_str("zlib preset dictionaries are not supported")
            }
            Self::Truncated => formatter.write_str("truncated zlib header or trailer"),
            Self::ChecksumMismatch { expected, actual } => write!(
                formatter,
                "zlib Adler-32 mismatch: expected {expected:#010x}, got {actual:#010x}"
            ),
            Self::TrailingGarbage => formatter.write_str("trailing data after zlib stream"),
        }
    }
}

/// The reason a DEFLATE stream was rejected.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DeflateErrorKind {
    /// The backend rejected the compressed stream.
    InvalidData,
    /// The stream unexpectedly requested a preset dictionary.
    UnexpectedDictionary,
    /// The backend returned an unexpected status code.
    BackendStatus(i32),
    /// No progress was possible before the end of the compressed input.
    Truncated,
    /// The decoder made no progress despite having input and output space.
    Stalled,
    /// Bytes remained after the final block of a raw DEFLATE stream.
    TrailingGarbage,
}

impl Display for DeflateErrorKind {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidData => formatter.write_str("invalid DEFLATE data"),
            Self::UnexpectedDictionary => {
                formatter.write_str("DEFLATE stream requested a preset dictionary")
            }
            Self::BackendStatus(status) => {
                write!(formatter, "unexpected DEFLATE backend status {status}")
            }
            Self::Truncated => formatter.write_str("truncated DEFLATE stream"),
            Self::Stalled => formatter.write_str("DEFLATE decoder made no progress"),
            Self::TrailingGarbage => formatter.write_str("trailing data after raw DEFLATE stream"),
        }
    }
}

/// A terminal decoding error.
///
/// This type is cloneable so a [`std::io::Read`] adapter can return the same
/// logical failure on every read after the pipeline has failed.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum DecodeError {
    /// A positional input read or output write failed.
    Io {
        /// Compressed offset, when the operation was tied to an input offset.
        offset: Option<u64>,
        /// Shared original I/O error.
        source: Arc<io::Error>,
    },
    /// The gzip framing was invalid.
    InvalidGzip {
        /// Compressed byte offset.
        offset: u64,
        /// Detailed reason.
        reason: GzipErrorKind,
    },
    /// Automatic detection found neither gzip nor zlib framing.
    UnrecognizedFormat,
    /// The zlib framing was invalid.
    InvalidZlib {
        /// Compressed byte offset.
        offset: u64,
        /// Detailed reason.
        reason: ZlibErrorKind,
    },
    /// The raw DEFLATE payload was invalid.
    InvalidDeflate {
        /// Best-known compressed bit offset.
        bit_offset: u64,
        /// Detailed reason.
        reason: DeflateErrorKind,
    },
    /// Structural analysis exhausted a configured budget or exact counter.
    Analysis {
        /// Typed resource or counter failure.
        reason: AnalysisErrorKind,
    },
    /// A member's CRC32 did not match its footer.
    ChecksumMismatch {
        /// Zero-based member number.
        member: u64,
        /// Footer value.
        expected: u32,
        /// Computed value.
        actual: u32,
    },
    /// A member's modulo-2^32 output size did not match its footer.
    SizeMismatch {
        /// Zero-based member number.
        member: u64,
        /// Footer value.
        expected: u32,
        /// Computed value.
        actual_mod32: u32,
    },
    /// Decoded output would exceed the configured limit.
    OutputLimitExceeded {
        /// Configured maximum decoded byte count.
        limit: u64,
    },
    /// Decoded output did not equal the caller's exact expectation.
    UnexpectedOutputSize {
        /// Required total output size.
        expected: u64,
        /// Observed total, or the total that the next handoff would produce.
        actual: u64,
    },
    /// Inflation did not reach an index checkpoint at its declared bit offset.
    IndexBoundaryMismatch {
        /// Compressed bit offset declared by the index.
        expected_bit_offset: u64,
        /// Compressed bit offset reached by the inflater.
        actual_bit_offset: u64,
    },
    /// A checkpoint's decompressed offset disagreed with decoded output.
    IndexOutputMismatch {
        /// Compressed checkpoint whose output offset was checked.
        checkpoint_bit_offset: u64,
        /// Decompressed bytes declared between the surrounding checkpoints.
        expected_bytes: u64,
        /// Decompressed bytes actually produced for that span.
        actual_bytes: u64,
    },
    /// A checkpoint's imported line counter disagreed with decoded output.
    IndexLineMismatch {
        /// Decompressed checkpoint whose line counter was checked.
        checkpoint_byte_offset: u64,
        /// Newline count declared by the index.
        expected_lines: u64,
        /// Newline count observed in ordered decoded output.
        actual_lines: u64,
    },
    /// An imported index's total line counter disagreed with decoded output.
    IndexTotalLineMismatch {
        /// Newline count declared by the index.
        expected_lines: u64,
        /// Newline count observed in the complete decoded output.
        actual_lines: u64,
    },
    /// A decoder worker panicked.
    WorkerPanicked,
    /// Decoding was cancelled because the consumer stopped.
    Cancelled,
}

impl DecodeError {
    pub(crate) fn input_io(offset: u64, source: io::Error) -> Self {
        Self::Io {
            offset: Some(offset),
            source: Arc::new(source),
        }
    }

    pub(crate) fn output_io(source: io::Error) -> Self {
        Self::Io {
            offset: None,
            source: Arc::new(source),
        }
    }

    pub(crate) fn io_kind(&self) -> io::ErrorKind {
        match self {
            Self::Io { source, .. } => source.kind(),
            Self::InvalidGzip {
                reason: GzipErrorKind::Truncated,
                ..
            }
            | Self::InvalidZlib {
                reason: ZlibErrorKind::Truncated,
                ..
            }
            | Self::InvalidDeflate {
                reason: DeflateErrorKind::Truncated,
                ..
            } => io::ErrorKind::UnexpectedEof,
            Self::InvalidGzip { .. }
            | Self::UnrecognizedFormat
            | Self::InvalidZlib { .. }
            | Self::InvalidDeflate { .. }
            | Self::ChecksumMismatch { .. }
            | Self::SizeMismatch { .. }
            | Self::UnexpectedOutputSize { .. }
            | Self::IndexBoundaryMismatch { .. }
            | Self::IndexOutputMismatch { .. }
            | Self::IndexLineMismatch { .. }
            | Self::IndexTotalLineMismatch { .. } => io::ErrorKind::InvalidData,
            Self::OutputLimitExceeded { .. }
            | Self::Analysis {
                reason:
                    AnalysisErrorKind::ResourceLimit { .. } | AnalysisErrorKind::AllocationFailed { .. },
            } => io::ErrorKind::FileTooLarge,
            Self::Analysis {
                reason: AnalysisErrorKind::CounterOverflow { .. },
            } => io::ErrorKind::InvalidData,
            Self::WorkerPanicked => io::ErrorKind::Other,
            Self::Cancelled => io::ErrorKind::Interrupted,
        }
    }

    pub(crate) fn to_io_error(&self) -> io::Error {
        io::Error::new(self.io_kind(), self.clone())
    }
}

impl Display for DecodeError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Io {
                offset: Some(offset),
                source,
            } => write!(
                formatter,
                "I/O error at compressed offset {offset}: {source}"
            ),
            Self::Io {
                offset: None,
                source,
            } => write!(formatter, "output I/O error: {source}"),
            Self::InvalidGzip { offset, reason } => {
                write!(formatter, "invalid gzip data at byte {offset}: {reason}")
            }
            Self::UnrecognizedFormat => {
                formatter.write_str("input is neither recognizable gzip nor zlib data")
            }
            Self::InvalidZlib { offset, reason } => {
                write!(formatter, "invalid zlib data at byte {offset}: {reason}")
            }
            Self::InvalidDeflate { bit_offset, reason } => {
                write!(
                    formatter,
                    "invalid DEFLATE data at bit {bit_offset}: {reason}"
                )
            }
            Self::Analysis { reason } => write!(formatter, "analysis failed: {reason}"),
            Self::ChecksumMismatch {
                member,
                expected,
                actual,
            } => write!(
                formatter,
                "gzip member {member} CRC32 mismatch: expected {expected:#010x}, got {actual:#010x}"
            ),
            Self::SizeMismatch {
                member,
                expected,
                actual_mod32,
            } => write!(
                formatter,
                "gzip member {member} ISIZE mismatch: expected {expected}, got {actual_mod32}"
            ),
            Self::OutputLimitExceeded { limit } => {
                write!(formatter, "decoded output exceeded the {limit}-byte limit")
            }
            Self::UnexpectedOutputSize { expected, actual } => write!(
                formatter,
                "decoded output size mismatch: expected {expected} bytes, got {actual}"
            ),
            Self::IndexBoundaryMismatch {
                expected_bit_offset,
                actual_bit_offset,
            } => write!(
                formatter,
                "index checkpoint at bit {expected_bit_offset} did not match the inflater boundary at bit {actual_bit_offset}"
            ),
            Self::IndexOutputMismatch {
                checkpoint_bit_offset,
                expected_bytes,
                actual_bytes,
            } => write!(
                formatter,
                "index span ending at bit {checkpoint_bit_offset} declared {expected_bytes} output bytes but produced {actual_bytes}"
            ),
            Self::IndexLineMismatch {
                checkpoint_byte_offset,
                expected_lines,
                actual_lines,
            } => write!(
                formatter,
                "index checkpoint at decoded byte {checkpoint_byte_offset} declared {expected_lines} preceding newlines but output contains {actual_lines}"
            ),
            Self::IndexTotalLineMismatch {
                expected_lines,
                actual_lines,
            } => write!(
                formatter,
                "index declared {expected_lines} total newlines but output contains {actual_lines}"
            ),
            Self::WorkerPanicked => formatter.write_str("a decoder worker panicked"),
            Self::Cancelled => formatter.write_str("decoding was cancelled"),
        }
    }
}

impl Error for DecodeError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Io { source, .. } => Some(source.as_ref()),
            _ => None,
        }
    }
}

/// Statistics produced after the complete stream has been verified.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct DecodeReport {
    /// Compressed bytes consumed.
    pub compressed_bytes: u64,
    /// Decompressed bytes emitted.
    pub decompressed_bytes: u64,
    /// Number of completed framing units: gzip members, or one zlib/raw stream.
    ///
    /// A raw-DEFLATE unit is structurally complete but has no container
    /// checksum to authenticate it.
    pub member_count: u64,
    /// Configured decoder-worker budget.
    pub decoder_threads: usize,
    /// Concrete container framing that was decoded.
    pub format: Format,
    /// Newline bytes in the decoded output when line counting was enabled.
    ///
    /// Enable this with
    /// [`DecoderBuilder::count_lines`](crate::DecoderBuilder::count_lines).
    /// The value counts `b'\n'` bytes; a final unterminated line does not add
    /// one to the count.
    pub line_count: Option<u64>,
}

impl AsRef<DecodeReport> for DecodeReport {
    fn as_ref(&self) -> &DecodeReport {
        self
    }
}

/// Result of a verified decode that also collected a random-access index.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IndexedDecodeReport {
    /// Scalar statistics for the verified decode.
    pub decode: DecodeReport,
    /// Random-access index built from authoritative decode boundaries.
    pub index: DeflateIndex,
}

impl IndexedDecodeReport {
    /// Returns the scalar decode report.
    #[must_use]
    pub const fn report(&self) -> &DecodeReport {
        &self.decode
    }

    /// Returns the collected random-access index.
    #[must_use]
    pub const fn index(&self) -> &DeflateIndex {
        &self.index
    }

    /// Separates the scalar report from the owning index.
    #[must_use]
    pub fn into_parts(self) -> (DecodeReport, DeflateIndex) {
        (self.decode, self.index)
    }
}

impl AsRef<DecodeReport> for IndexedDecodeReport {
    fn as_ref(&self) -> &DecodeReport {
        &self.decode
    }
}

/// Failure of an operation that decodes and builds an index.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum IndexingError {
    /// The compressed input could not be decoded and verified.
    Decode(DecodeError),
    /// The index could not be constructed or finalized.
    Index(IndexError),
}

/// Failure while decoding through a caller-supplied index.
///
/// Index-driven decoding is strict: an invalid, incomplete, or source-mismatched
/// index is reported rather than silently falling back to an unindexed path.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum IndexDecodeError {
    /// The compressed input could not be decoded and verified.
    Decode(DecodeError),
    /// The supplied index was invalid or did not describe the source.
    Index(IndexError),
    /// The decoder's selected container disagreed with the index provenance.
    FormatMismatch {
        /// Container selected on the decoder builder.
        selected: Format,
        /// Container provenance recorded by the index.
        indexed: IndexKind,
    },
}

impl From<DecodeError> for IndexDecodeError {
    fn from(error: DecodeError) -> Self {
        Self::Decode(error)
    }
}

impl From<IndexError> for IndexDecodeError {
    fn from(error: IndexError) -> Self {
        Self::Index(error)
    }
}

impl Display for IndexDecodeError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Decode(error) => Display::fmt(error, formatter),
            Self::Index(error) => {
                write!(formatter, "index-driven decode rejected the index: {error}")
            }
            Self::FormatMismatch { selected, indexed } => write!(
                formatter,
                "decoder selected {selected}, but the index describes {indexed:?} data"
            ),
        }
    }
}

impl Error for IndexDecodeError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Decode(error) => Some(error),
            Self::Index(error) => Some(error),
            Self::FormatMismatch { .. } => None,
        }
    }
}

impl IndexingError {
    pub(crate) fn to_io_error(&self) -> io::Error {
        match self {
            Self::Decode(error) => error.to_io_error(),
            Self::Index(_) => io::Error::other(self.clone()),
        }
    }
}

impl From<DecodeError> for IndexingError {
    fn from(error: DecodeError) -> Self {
        Self::Decode(error)
    }
}

impl From<IndexError> for IndexingError {
    fn from(error: IndexError) -> Self {
        Self::Index(error)
    }
}

impl Display for IndexingError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Decode(error) => Display::fmt(error, formatter),
            Self::Index(error) => write!(formatter, "index construction failed: {error}"),
        }
    }
}

impl Error for IndexingError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Decode(error) => Some(error),
            Self::Index(error) => Some(error),
        }
    }
}