Skip to main content

ferro_lumberjack/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Error types.
3//!
4//! Two distinct error families:
5//!
6//! - [`FrameError`] — pure codec errors (no I/O). Returned by the
7//!   [`crate::frame::FrameDecoder`] and the encoder helpers.
8//! - [`ProtocolError`] — higher-level errors emitted by the
9//!   client (transport, TLS, ACK validation). Wraps `FrameError`
10//!   and `std::io::Error`.
11
12use thiserror::Error;
13
14/// Errors that can occur while encoding or decoding a Lumberjack v2 frame.
15///
16/// All variants are non-exhaustive in spirit: future versions of the codec
17/// may add new variants without bumping the major version while in the
18/// `v0.0.x` series.
19#[derive(Debug, Error)]
20#[non_exhaustive]
21pub enum FrameError {
22    /// Wire byte 0 is not `b'2'`. The decoder refuses to advance because
23    /// it cannot determine which protocol version applies.
24    #[error("unsupported lumberjack version byte: {0:#x} (expected 0x32 / '2')")]
25    UnsupportedVersion(u8),
26
27    /// Wire byte 1 is not a known frame type.
28    #[error("unknown lumberjack frame type: {0:#x}")]
29    UnknownFrameType(u8),
30
31    /// A length field declared a frame larger than
32    /// [`crate::DEFAULT_MAX_FRAME_PAYLOAD`] (or the configured per-decoder
33    /// limit). Surfaces resource-exhaustion attacks before the decoder
34    /// allocates.
35    #[error("frame payload length {requested} exceeds configured limit of {limit} bytes")]
36    PayloadTooLarge {
37        /// Length declared on the wire.
38        requested: usize,
39        /// Configured cap for this decoder.
40        limit: usize,
41    },
42
43    /// Decompression failed (`C` frame had invalid zlib content).
44    #[error("zlib decompression failed: {0}")]
45    Decompression(String),
46
47    /// The decompressed contents of a `C` frame would exceed the
48    /// per-decoder size limit. Caps zlib-bomb input.
49    #[error("decompressed payload would exceed limit of {limit} bytes")]
50    DecompressedTooLarge {
51        /// Configured cap for this decoder.
52        limit: usize,
53    },
54
55    /// The compressor returned an error while encoding (typically out of
56    /// memory). Returned only by [`crate::encode_compressed`].
57    #[error("zlib compression failed: {0}")]
58    Compression(String),
59}
60
61/// Errors emitted by the high-level client.
62#[derive(Debug, Error)]
63#[non_exhaustive]
64pub enum ProtocolError {
65    /// I/O error while reading from or writing to the network.
66    #[error("io: {0}")]
67    Io(#[from] std::io::Error),
68
69    /// Codec error (frame parse / encode failure).
70    #[error("codec: {0}")]
71    Codec(#[from] FrameError),
72
73    /// Operation timed out (connect, write, or ACK read).
74    #[error("operation timed out: {0}")]
75    Timeout(&'static str),
76
77    /// The receiver's ACK had an unexpected version or frame type, or its
78    /// sequence number didn't match what the client just sent.
79    #[error(
80        "unexpected ack: version=0x{version:02x} type=0x{frame_type:02x} seq={acked_seq} (expected_last_seq={expected_seq})"
81    )]
82    UnexpectedAck {
83        /// Wire-byte 0.
84        version: u8,
85        /// Wire-byte 1.
86        frame_type: u8,
87        /// Sequence number on the ACK frame.
88        acked_seq: u32,
89        /// Sequence number this client expected the ACK to reference.
90        expected_seq: u32,
91    },
92
93    /// The receiver acknowledged fewer events than were sent (partial ACK).
94    /// Surfaces so the caller can decide whether to retry the unacked tail.
95    #[error("partial ack: {acked} of {sent} events acknowledged")]
96    PartialAck {
97        /// Number of events the receiver acknowledged.
98        acked: u32,
99        /// Number of events the client sent in this window.
100        sent: u32,
101    },
102
103    /// No hosts were configured for the client.
104    #[error("no hosts configured")]
105    NoHostsConfigured,
106
107    /// All configured hosts have been tried and all failed; the wrapped
108    /// error is the most recent failure.
109    #[error("all configured hosts failed; last error: {0}")]
110    AllHostsFailed(Box<Self>),
111
112    /// TLS configuration error (key/cert load, server-name parse, …).
113    #[cfg(feature = "tls")]
114    #[error("tls config: {0}")]
115    Tls(String),
116}