bufkit/
error.rs

1#[cfg(feature = "varing")]
2#[cfg_attr(docsrs, doc(cfg(feature = "varing")))]
3pub use varing::{DecodeError as ReadVarintError, EncodeError as WriteVarintError};
4
5use core::num::NonZeroUsize;
6
7macro_rules! try_op_error {
8  (
9    #[doc = $doc:literal]
10    #[error($msg:literal)]
11    $op:ident
12  ) => {
13    paste::paste! {
14      #[doc = $doc]
15      #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
16      #[error($msg)]
17      pub struct [< Try $op:camel Error >] {
18        requested: NonZeroUsize,
19        available: usize,
20      }
21
22      impl [< Try $op:camel Error >] {
23        #[doc = "Creates a new `Try" $op:camel "Error` with the requested and available bytes."]
24        ///
25        /// # Panics
26        ///
27        /// - In debug builds, panics if `requested <= available` (this would not be an error condition).
28        /// - The `requested` value must be a non-zero.
29        #[inline]
30        pub const fn new(requested: usize, available: usize) -> Self {
31          debug_assert!(requested > available, concat!(stringify!([< Try $op:camel Error >]), ": requested must be greater than available"));
32
33          Self {
34            requested: NonZeroUsize::new(requested).expect(
35              concat!(stringify!([< Try $op:camel Error >]), ": requested must be non-zero")
36            ),
37            available,
38          }
39        }
40
41        /// Returns the number of bytes requested for the operation.
42        ///
43        /// This is the minimum number of bytes needed for the operation to succeed.
44        #[inline]
45        pub const fn requested(&self) -> usize {
46          self.requested.get()
47        }
48
49        /// Returns the number of bytes available in the buffer.
50        ///
51        /// This is the actual number of bytes that were available when the operation failed.
52        #[inline]
53        pub const fn available(&self) -> usize {
54          self.available
55        }
56
57        /// Returns the number of additional bytes needed for the operation to succeed.
58        ///
59        /// This is equivalent to `requested() - available()`.
60        #[inline]
61        pub const fn shortage(&self) -> usize {
62          self.requested() - self.available()
63        }
64      }
65    }
66  };
67}
68
69try_op_error!(
70  #[doc = "An error that occurs when trying to advance the buffer cursor beyond available data.
71  
72This error indicates that an attempt was made to move the buffer's read position forward
73by more bytes than are currently available. This is a recoverable error - the buffer
74position remains unchanged and the operation can be retried with a smaller advance amount."]
75  #[error(
76    "not enough bytes available to advance (requested {requested} but only {available} available)"
77  )]
78  advance
79);
80
81#[cfg(feature = "std")]
82impl From<TryAdvanceError> for std::io::Error {
83  fn from(e: TryAdvanceError) -> Self {
84    std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e)
85  }
86}
87
88try_op_error!(
89  #[doc = "An error that occurs when trying to read data from a buffer with insufficient bytes.
90  
91This error indicates that a read operation failed because the buffer does not contain
92enough bytes to satisfy the request. Failed read operations do not consume any bytes - the buffer position remains unchanged."]
93  #[error(
94    "not enough bytes available to read value (requested {requested} but only {available} available)"
95  )]
96  read
97);
98
99#[cfg(feature = "std")]
100impl From<TryReadError> for std::io::Error {
101  fn from(e: TryReadError) -> Self {
102    std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e)
103  }
104}
105
106try_op_error!(
107  #[doc = "An error that occurs when trying to peek at data beyond the buffer's available bytes.
108  
109This error indicates that a peek operation failed because the buffer does not contain
110enough bytes at the current position. Peek operations never modify the buffer position,
111so this error leaves the buffer in its original state."]
112  #[error(
113    "not enough bytes available to peek value (requested {requested} but only {available} available)"
114  )]
115  peek
116);
117
118#[cfg(feature = "std")]
119impl From<TryPeekError> for std::io::Error {
120  fn from(e: TryPeekError) -> Self {
121    std::io::Error::new(std::io::ErrorKind::UnexpectedEof, e)
122  }
123}
124
125impl From<TryPeekError> for TryReadError {
126  #[inline]
127  fn from(e: TryPeekError) -> Self {
128    TryReadError {
129      requested: e.requested,
130      available: e.available,
131    }
132  }
133}
134
135try_op_error!(
136  #[doc = "An error that occurs when trying to write data to a buffer with insufficient space.
137  
138This error indicates that a write operation failed because the buffer does not have
139enough remaining capacity to hold the data.
140
141This error is particularly useful for Sans-I/O designs as it provides exact information
142about space requirements, allowing the caller to allocate a larger buffer and retry:
143
144```rust
145# use bufkit::BufMut;
146let mut small_buf = [0u8; 4];
147let mut writer = &mut small_buf[..];
148
149match writer.try_put_u64_le(0x123456789ABCDEF0) {
150    Err(e) => {
151        // Caller knows exactly how much space is needed
152        assert_eq!(e.requested(), 8);
153        assert_eq!(e.available(), 4);
154        println!(\"Need {} more bytes\", e.shortage());
155    }
156    _ => panic!(\"Expected error\"),
157}
158```"]
159  #[error(
160    "not enough space available to write value (requested {requested} but only {available} available)"
161  )]
162  write
163);
164
165#[cfg(feature = "std")]
166impl From<TryWriteError> for std::io::Error {
167  fn from(e: TryWriteError) -> Self {
168    std::io::Error::new(std::io::ErrorKind::WriteZero, e)
169  }
170}
171
172/// An error that occurs when trying to create a segment with an invalid range.
173///
174/// This error indicates that the requested range extends beyond the buffer's boundaries
175/// or is otherwise invalid (e.g., start > end). The original buffer remains unchanged.
176///
177/// # Examples
178///
179/// ```rust
180/// # use bufkit::Buf;
181/// let data = b"Hello";
182/// let buf = &data[..];
183///
184/// // Range extends beyond buffer
185/// match buf.try_segment(2..10) {
186///     Err(e) => {
187///         assert_eq!(e.start(), 2);
188///         assert_eq!(e.end(), 10);
189///         assert_eq!(e.available(), 5);
190///     }
191///     _ => panic!("Expected error"),
192/// }
193///
194/// // Invalid range (start > end)
195/// assert!(buf.try_segment(4..2).is_err());
196/// ```
197#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
198#[error("invalid segment range {start}..{end} for buffer with {available} bytes")]
199pub struct TrySegmentError {
200  start: usize,
201  end: usize,
202  available: usize,
203}
204
205impl TrySegmentError {
206  /// Creates a new `TrySegmentError`.
207  ///
208  /// # Panics
209  ///
210  /// In debug builds, panics if the range is valid (would not be an error) or the available bytes are less than the requested range.
211  #[inline]
212  pub const fn new(start: usize, end: usize, available: usize) -> Self {
213    debug_assert!(
214      start > end || end > available,
215      "TrySegmentError: invalid error - range is valid"
216    );
217
218    Self {
219      start,
220      end,
221      available,
222    }
223  }
224
225  /// Returns the start index of the requested range.
226  #[inline]
227  pub const fn start(&self) -> usize {
228    self.start
229  }
230
231  /// Returns the end index of the requested range (exclusive).
232  #[inline]
233  pub const fn end(&self) -> usize {
234    self.end
235  }
236
237  /// Returns the total number of bytes available in the buffer.
238  #[inline]
239  pub const fn available(&self) -> usize {
240    self.available
241  }
242
243  /// Returns the length of the requested range.
244  ///
245  /// Returns 0 if start > end (invalid range).
246  #[inline]
247  pub const fn requested(&self) -> usize {
248    self.end.saturating_sub(self.start)
249  }
250
251  /// Returns whether the range itself is invalid (start > end).
252  #[inline]
253  pub const fn is_inverted(&self) -> bool {
254    self.start > self.end
255  }
256
257  /// Returns how many bytes the range extends beyond the buffer.
258  ///
259  /// Returns 0 if the range doesn't extend beyond the buffer
260  /// (e.g., when the error is due to start > end).
261  #[inline]
262  pub const fn overflow(&self) -> usize {
263    self.end.saturating_sub(self.available)
264  }
265}
266
267#[cfg(feature = "std")]
268impl From<TrySegmentError> for std::io::Error {
269  fn from(e: TrySegmentError) -> Self {
270    std::io::Error::new(std::io::ErrorKind::InvalidInput, e)
271  }
272}
273
274/// An error that occurs when an offset is beyond the buffer's boundaries.
275///
276/// This error is typically used for operations that access a specific position
277/// in the buffer, such as writing at an offset or splitting at a position.
278///
279/// # Example
280///
281/// ```rust
282/// # use bufkit::BufMut;
283/// let mut buf = [0u8; 10];
284/// let mut writer = &mut buf[..];
285///
286/// // Trying to write at offset 15 in a 10-byte buffer
287/// match writer.try_put_u32_le_at(42, 15) {
288///     Err(e) => {
289///         // Error contains OutOfBounds information
290///     }
291///     _ => panic!("Expected error"),
292/// }
293/// ```
294#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
295#[error("offset {offset} is out of bounds for buffer length {length}")]
296pub struct OutOfBounds {
297  offset: usize,
298  length: usize,
299}
300
301impl OutOfBounds {
302  /// Creates a new `OutOfBounds` error.
303  ///
304  /// # Panics
305  ///
306  /// In debug builds, panics if `offset < length` (would not be out of bounds).
307  #[inline]
308  pub const fn new(offset: usize, length: usize) -> Self {
309    debug_assert!(offset >= length, "OutOfBounds: offset must be >= length");
310
311    Self { offset, length }
312  }
313
314  /// Returns the offset that caused the error.
315  #[inline]
316  pub const fn offset(&self) -> usize {
317    self.offset
318  }
319
320  /// Returns the actual length of the buffer.
321  #[inline]
322  pub const fn length(&self) -> usize {
323    self.length
324  }
325
326  /// Returns how far beyond the buffer the offset extends.
327  ///
328  /// This is equivalent to `offset() - length() + 1`.
329  #[inline]
330  pub const fn excess(&self) -> usize {
331    self.offset() - self.length() + 1
332  }
333}
334
335#[cfg(feature = "std")]
336impl From<OutOfBounds> for std::io::Error {
337  fn from(e: OutOfBounds) -> Self {
338    std::io::Error::new(std::io::ErrorKind::InvalidInput, e)
339  }
340}
341
342/// An error indicating insufficient space at a specific offset in a buffer.
343///
344/// This error provides detailed information about space requirements when a write
345/// operation fails due to insufficient remaining capacity from a given offset.
346#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
347#[error(
348  "not enough bytes available at {offset} (requested {requested} but only {available} available)"
349)]
350pub struct InsufficientSpaceAt {
351  /// The number of bytes requested to write.
352  requested: NonZeroUsize,
353  /// The number of bytes available from the offset to the end of buffer.
354  available: usize,
355  /// The offset at which the write was attempted.
356  offset: usize,
357}
358
359impl InsufficientSpaceAt {
360  /// Creates a new `InsufficientSpaceAt` error.
361  ///
362  /// # Panics
363  ///
364  /// - In debug builds, panics if `requested <= available` (would not be an error).
365  /// - The `requested` value must be a non-zero.
366  #[inline]
367  pub const fn new(requested: usize, available: usize, offset: usize) -> Self {
368    debug_assert!(
369      requested > available,
370      "InsufficientSpaceAt: requested must be greater than available"
371    );
372
373    Self {
374      requested: NonZeroUsize::new(requested)
375        .expect("InsufficientSpaceAt: requested must be non-zero"),
376      available,
377      offset,
378    }
379  }
380
381  /// Returns the number of bytes requested to write.
382  #[inline]
383  pub const fn requested(&self) -> usize {
384    self.requested.get()
385  }
386
387  /// Returns the number of bytes available from the offset.
388  #[inline]
389  pub const fn available(&self) -> usize {
390    self.available
391  }
392
393  /// Returns the offset at which the write was attempted.
394  #[inline]
395  pub const fn offset(&self) -> usize {
396    self.offset
397  }
398
399  /// Returns the number of additional bytes needed for the operation to succeed.
400  ///
401  /// This is equivalent to `requested() - available()`.
402  #[inline]
403  pub const fn shortage(&self) -> usize {
404    self.requested() - self.available()
405  }
406}
407
408/// An error that occurs when trying to write at a specific offset in the buffer.
409///
410/// This error is returned when the offset is out of bounds or when there is insufficient space to write the requested data.
411#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
412#[non_exhaustive]
413pub enum TryWriteAtError {
414  /// An error that occurs when trying to write at an offset that is out of bounds for the buffer.
415  #[error(transparent)]
416  OutOfBounds(#[from] OutOfBounds),
417  /// An error that occurs when there is not enough space in the buffer to write the requested data.
418  #[error(transparent)]
419  InsufficientSpace(#[from] InsufficientSpaceAt),
420}
421
422impl TryWriteAtError {
423  /// Creates a new `TryWriteAtError::OutOfBounds` error.
424  #[inline]
425  pub const fn out_of_bounds(offset: usize, length: usize) -> Self {
426    Self::OutOfBounds(OutOfBounds::new(offset, length))
427  }
428
429  /// Creates a new `TryWriteAtError::InsufficientSpace` error.
430  ///
431  /// # Panics
432  ///
433  /// - In debug builds, panics if `requested <= available` (would not be an error).
434  /// - The `requested` value must be a non-zero.
435  #[inline]
436  pub const fn insufficient_space(requested: usize, available: usize, offset: usize) -> Self {
437    Self::InsufficientSpace(InsufficientSpaceAt::new(requested, available, offset))
438  }
439}
440
441#[cfg(feature = "std")]
442impl From<TryWriteAtError> for std::io::Error {
443  fn from(e: TryWriteAtError) -> Self {
444    match e {
445      TryWriteAtError::OutOfBounds(e) => std::io::Error::new(std::io::ErrorKind::InvalidInput, e),
446      TryWriteAtError::InsufficientSpace(e) => {
447        if e.offset() >= e.available() {
448          std::io::Error::new(std::io::ErrorKind::InvalidInput, e)
449        } else {
450          std::io::Error::new(std::io::ErrorKind::WriteZero, e)
451        }
452      }
453    }
454  }
455}
456
457/// An error that occurs when trying to write type in LEB128 format at a specific offset in the buffer.
458#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
459#[non_exhaustive]
460#[cfg(feature = "varing")]
461#[cfg_attr(docsrs, doc(cfg(feature = "varing")))]
462pub enum WriteVarintAtError {
463  /// The offset is out of bounds for the buffer length.
464  #[error(transparent)]
465  OutOfBounds(#[from] OutOfBounds),
466  /// The buffer does not have enough capacity to encode the value.
467  #[error(transparent)]
468  InsufficientSpace(#[from] InsufficientSpaceAt),
469  /// A custom error message.
470  #[error("{0}")]
471  #[cfg(not(any(feature = "std", feature = "alloc")))]
472  Other(&'static str),
473
474  /// A custom error message.
475  #[error("{0}")]
476  #[cfg(any(feature = "std", feature = "alloc"))]
477  Other(std::borrow::Cow<'static, str>),
478}
479
480#[cfg(feature = "varing")]
481impl WriteVarintAtError {
482  /// Creates a new `WriteVarintAtError::OutOfBounds` error.
483  #[inline]
484  pub const fn out_of_bounds(offset: usize, length: usize) -> Self {
485    Self::OutOfBounds(OutOfBounds::new(offset, length))
486  }
487
488  /// Creates a new `WriteVarintAtError::Insufficient` error.
489  ///
490  /// # Panics
491  ///
492  /// - In debug builds, panics if `requested <= available` (would not be an error).
493  /// - The `requested` value must be a non-zero.
494  #[inline]
495  pub const fn insufficient_space(requested: usize, available: usize, offset: usize) -> Self {
496    Self::InsufficientSpace(InsufficientSpaceAt::new(requested, available, offset))
497  }
498
499  /// Creates a new `WriteVarintAtError` error from `WriteVarintError`.
500  #[inline]
501  pub const fn from_write_varint_error(err: WriteVarintError, offset: usize) -> Self {
502    match err {
503      WriteVarintError::InsufficientSpace(e) => {
504        Self::insufficient_space(e.requested(), e.available(), offset)
505      }
506      #[cfg(any(feature = "std", feature = "alloc"))]
507      WriteVarintError::Other(msg) => Self::Other(std::borrow::Cow::Borrowed(msg)),
508      #[cfg(not(any(feature = "std", feature = "alloc")))]
509      WriteVarintError::Other(msg) => Self::other(msg),
510      #[cfg(any(feature = "std", feature = "alloc"))]
511      _ => Self::Other(std::borrow::Cow::Borrowed("unknown error")),
512      #[cfg(not(any(feature = "std", feature = "alloc")))]
513      _ => Self::Other("unknown error"),
514    }
515  }
516
517  /// Creates a new `WriteVarintAtError::Other` error.
518  #[cfg(not(any(feature = "std", feature = "alloc")))]
519  #[inline]
520  pub const fn other(msg: &'static str) -> Self {
521    Self::Other(msg)
522  }
523
524  /// Creates a new `WriteVarintAtError::Other` error.
525  #[cfg(any(feature = "std", feature = "alloc"))]
526  #[inline]
527  pub fn other(msg: impl Into<std::borrow::Cow<'static, str>>) -> Self {
528    Self::Other(msg.into())
529  }
530}
531
532#[cfg(all(feature = "varing", feature = "std"))]
533impl From<WriteVarintAtError> for std::io::Error {
534  fn from(e: WriteVarintAtError) -> Self {
535    match e {
536      WriteVarintAtError::OutOfBounds(e) => {
537        std::io::Error::new(std::io::ErrorKind::InvalidInput, e)
538      }
539      WriteVarintAtError::InsufficientSpace(e) => {
540        if e.offset() >= e.available() {
541          std::io::Error::new(std::io::ErrorKind::InvalidInput, e)
542        } else {
543          std::io::Error::new(std::io::ErrorKind::WriteZero, e)
544        }
545      }
546      WriteVarintAtError::Other(msg) => std::io::Error::other(msg),
547    }
548  }
549}
550
551#[cfg(feature = "bytes_1")]
552const _: () = {
553  use bytes_1::TryGetError;
554
555  impl From<TryGetError> for TryAdvanceError {
556    fn from(e: TryGetError) -> Self {
557      TryAdvanceError::new(e.requested, e.available)
558    }
559  }
560
561  impl From<TryGetError> for TryReadError {
562    fn from(e: TryGetError) -> Self {
563      TryReadError::new(e.requested, e.available)
564    }
565  }
566};