Skip to main content

kacrab_protocol/record/
error.rs

1//! Error types for [`crate::record`].
2//!
3//! Demonstrates the canonical `struct + Kind` shape from the project research:
4//! `RecordError` carries shared context (`base_offset`) that's useful for any
5//! variant; `RecordErrorKind` enumerates the specific failure modes.
6
7use crate::{compression::CompressionError, crc::CrcMismatch, primitives::PrimitiveError};
8
9/// Error from record-batch / record / header read or write.
10#[derive(Debug, thiserror::Error)]
11#[error("record batch failed at base_offset {base_offset}")]
12#[non_exhaustive]
13pub struct RecordError {
14    /// `base_offset` of the batch being processed, or `-1` if unknown.
15    pub base_offset: i64,
16    /// What specifically went wrong.
17    #[source]
18    pub kind: RecordErrorKind,
19}
20
21impl RecordError {
22    /// Construct a `RecordError` with a known `base_offset`.
23    #[must_use]
24    pub const fn at_offset(base_offset: i64, kind: RecordErrorKind) -> Self {
25        Self { base_offset, kind }
26    }
27
28    /// Construct a `RecordError` when the offset is not yet known.
29    #[must_use]
30    pub const fn unknown_offset(kind: RecordErrorKind) -> Self {
31        Self {
32            base_offset: -1,
33            kind,
34        }
35    }
36}
37
38impl From<RecordErrorKind> for RecordError {
39    fn from(kind: RecordErrorKind) -> Self {
40        Self::unknown_offset(kind)
41    }
42}
43
44impl From<PrimitiveError> for RecordError {
45    fn from(err: PrimitiveError) -> Self {
46        Self::unknown_offset(RecordErrorKind::Primitive(err))
47    }
48}
49
50impl From<CrcMismatch> for RecordError {
51    fn from(err: CrcMismatch) -> Self {
52        Self::unknown_offset(RecordErrorKind::Crc(err))
53    }
54}
55
56impl From<CompressionError> for RecordError {
57    fn from(err: CompressionError) -> Self {
58        Self::unknown_offset(RecordErrorKind::Compression(err))
59    }
60}
61
62/// Specific reason a record-batch operation failed.
63#[derive(Debug, thiserror::Error)]
64#[non_exhaustive]
65pub enum RecordErrorKind {
66    /// Underlying primitive read failed.
67    #[error(transparent)]
68    Primitive(#[from] PrimitiveError),
69
70    /// CRC32C verification failed.
71    #[error(transparent)]
72    Crc(#[from] CrcMismatch),
73
74    /// Compression codec dispatch / decode failed.
75    #[error(transparent)]
76    Compression(#[from] CompressionError),
77
78    /// Magic byte is not 2 (v2 record batches only).
79    #[error("unsupported magic byte {0}, expected 2")]
80    UnsupportedMagic(i8),
81
82    /// `batchLength` is below the minimum (`BATCH_HEADER_SIZE = 49`).
83    #[error("batch length {got} below minimum {min}")]
84    BatchTooSmall {
85        /// Length declared on the wire.
86        got: i32,
87        /// Minimum legal length.
88        min: i32,
89    },
90
91    /// `recordCount` exceeds [`crate::record::MAX_RECORDS_PER_BATCH`].
92    #[error("record count {got} exceeds maximum {max}")]
93    RecordCountTooLarge {
94        /// Count declared on the wire.
95        got: i32,
96        /// Configured maximum.
97        max: usize,
98    },
99
100    /// A varint length field was negative where it must be `>= 0`.
101    #[error("negative {field} length {length}")]
102    NegativeLength {
103        /// Which field had the bad length (e.g. `"header key"`, `"record body"`).
104        field: &'static str,
105        /// The negative length read.
106        length: i32,
107    },
108
109    /// A length field exceeds the remaining buffer.
110    #[error("{field} length {got} exceeds remaining {remaining}")]
111    LengthOverflow {
112        /// Which field overflowed.
113        field: &'static str,
114        /// Length declared on the wire.
115        got: usize,
116        /// Bytes actually remaining in the buffer.
117        remaining: usize,
118    },
119}