Skip to main content

smol_bytes/
error.rs

1use core::ops::{Bound, RangeBounds};
2
3/// The error returned by [`bytes::Buf`] fallible readers when allocation is
4/// available.
5///
6/// This is re-exported separately because [`TryGetError`] is the crate-owned
7/// error used by [`crate::Buffer`]'s inherent API.
8#[cfg(any(feature = "std", feature = "alloc"))]
9pub use bytes::TryGetError as BufTryGetError;
10
11/// Error type for the `try_get_` methods of [`crate::Buffer`].
12/// Indicates that there were not enough remaining
13/// bytes in the buffer while attempting
14/// to get a value from a [`crate::Buffer`] with one
15/// of the `try_get_` methods.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct TryGetError {
18  /// The number of bytes necessary to get the value
19  pub requested: usize,
20
21  /// The number of bytes available in the buffer
22  pub available: usize,
23}
24
25impl core::fmt::Display for TryGetError {
26  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
27    write!(
28      f,
29      "Not enough bytes remaining in buffer to read value (requested {} but only {} available)",
30      self.requested, self.available
31    )
32  }
33}
34
35impl core::error::Error for TryGetError {}
36
37#[cfg(any(feature = "std", feature = "alloc"))]
38impl From<bytes::TryGetError> for TryGetError {
39  fn from(value: bytes::TryGetError) -> Self {
40    Self {
41      requested: value.requested,
42      available: value.available,
43    }
44  }
45}
46
47#[cfg(any(feature = "std", feature = "alloc"))]
48impl From<TryGetError> for bytes::TryGetError {
49  fn from(value: TryGetError) -> Self {
50    Self {
51      requested: value.requested,
52      available: value.available,
53    }
54  }
55}
56
57#[cfg(feature = "std")]
58impl From<TryGetError> for std::io::Error {
59  fn from(value: TryGetError) -> Self {
60    std::io::Error::other(value)
61  }
62}
63
64/// Error type for the `try_put_` methods of [`crate::Buffer`].
65/// Indicates that there were not enough remaining
66/// capacity in the buffer while attempting
67/// to put a value to a [`crate::Buffer`] with one
68/// of the `try_put_` methods.
69#[derive(Debug, PartialEq, Eq)]
70pub struct TryPutError {
71  /// The number of bytes necessary to put the value
72  pub requested: usize,
73
74  /// The number of bytes available in the buffer
75  pub available: usize,
76}
77
78impl core::fmt::Display for TryPutError {
79  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
80    write!(
81      f,
82      "Not enough bytes remaining in buffer to write value (requested {} but only {} available)",
83      self.requested, self.available
84    )
85  }
86}
87
88impl core::error::Error for TryPutError {}
89
90#[cfg(feature = "std")]
91impl From<TryPutError> for std::io::Error {
92  fn from(value: TryPutError) -> Self {
93    std::io::Error::other(value)
94  }
95}
96
97/// Error type indicating an invalid integer length was specified.
98/// This is used when attempting to read or write
99/// an integer with an invalid number of bytes.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101pub struct InvalidIntegerLength(pub usize);
102
103impl core::fmt::Display for InvalidIntegerLength {
104  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
105    write!(
106      f,
107      "invalid integer length: {} (must be less or equal to 8)",
108      self.0
109    )
110  }
111}
112
113impl core::error::Error for InvalidIntegerLength {}
114
115#[cfg(feature = "std")]
116impl From<InvalidIntegerLength> for std::io::Error {
117  fn from(value: InvalidIntegerLength) -> Self {
118    std::io::Error::new(std::io::ErrorKind::InvalidInput, value)
119  }
120}
121
122impl From<InvalidIntegerLength> for TryPutIntegerError {
123  fn from(value: InvalidIntegerLength) -> Self {
124    TryPutIntegerError::InvalidLength(value)
125  }
126}
127
128/// Error returned by checked variable-width integer writers.
129///
130/// Unlike [`TryPutIntegerError`], this error distinguishes values that cannot
131/// be represented in the requested width from values that merely need more
132/// writable capacity.
133#[derive(Debug, PartialEq, Eq)]
134pub enum TryPutIntegerCheckedError {
135  /// The requested integer width was outside the supported `0..=8` range.
136  InvalidLength(InvalidIntegerLength),
137  /// The value cannot be represented in the requested width.
138  OutOfRange {
139    /// The value supplied by the caller.
140    value: i128,
141    /// The smallest value representable by the requested width.
142    min: i128,
143    /// The largest value representable by the requested width.
144    max: i128,
145    /// The requested width in bytes.
146    nbytes: usize,
147  },
148  /// There was not enough writable capacity for the checked value.
149  NotEnoughSpace(TryPutError),
150}
151
152impl core::fmt::Display for TryPutIntegerCheckedError {
153  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
154    match self {
155      Self::InvalidLength(error) => error.fmt(f),
156      Self::OutOfRange {
157        value,
158        min,
159        max,
160        nbytes,
161      } => write!(
162        f,
163        "value {value} is out of range for a {nbytes}-byte integer ({min}..={max})"
164      ),
165      Self::NotEnoughSpace(error) => error.fmt(f),
166    }
167  }
168}
169
170impl core::error::Error for TryPutIntegerCheckedError {
171  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
172    match self {
173      Self::InvalidLength(error) => Some(error),
174      Self::OutOfRange { .. } => None,
175      Self::NotEnoughSpace(error) => Some(error),
176    }
177  }
178}
179
180impl From<InvalidIntegerLength> for TryPutIntegerCheckedError {
181  fn from(value: InvalidIntegerLength) -> Self {
182    Self::InvalidLength(value)
183  }
184}
185
186impl From<TryPutError> for TryPutIntegerCheckedError {
187  fn from(value: TryPutError) -> Self {
188    Self::NotEnoughSpace(value)
189  }
190}
191
192/// Error returned by variable-width integer readers.
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
194pub enum TryGetIntegerError {
195  /// Not enough bytes remained to read the requested value.
196  NotEnoughBytes(TryGetError),
197  /// The requested integer width was outside the supported `0..=8` range.
198  InvalidLength(InvalidIntegerLength),
199}
200
201impl core::fmt::Display for TryGetIntegerError {
202  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
203    match self {
204      Self::NotEnoughBytes(error) => error.fmt(f),
205      Self::InvalidLength(error) => error.fmt(f),
206    }
207  }
208}
209
210impl core::error::Error for TryGetIntegerError {
211  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
212    match self {
213      Self::NotEnoughBytes(error) => Some(error),
214      Self::InvalidLength(error) => Some(error),
215    }
216  }
217}
218
219impl From<TryGetError> for TryGetIntegerError {
220  fn from(value: TryGetError) -> Self {
221    Self::NotEnoughBytes(value)
222  }
223}
224
225impl From<InvalidIntegerLength> for TryGetIntegerError {
226  fn from(value: InvalidIntegerLength) -> Self {
227    Self::InvalidLength(value)
228  }
229}
230
231#[cfg(feature = "std")]
232impl From<TryGetIntegerError> for std::io::Error {
233  fn from(value: TryGetIntegerError) -> Self {
234    std::io::Error::other(value)
235  }
236}
237
238/// Error type for the `try_put_` methods of [`crate::Buffer`].
239/// Indicates that there were not enough remaining
240/// capacity in the buffer while attempting
241/// to put a value to a [`crate::Buffer`] with one
242/// of the `try_put_` methods.
243#[derive(Debug, PartialEq, Eq)]
244pub enum TryPutIntegerError {
245  /// Not enough space to write all bytes.
246  NotEnoughSpace(TryPutError),
247  /// Invalid number of bytes specified.
248  InvalidLength(InvalidIntegerLength),
249}
250
251impl core::fmt::Display for TryPutIntegerError {
252  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
253    match self {
254      Self::NotEnoughSpace(err) => write!(f, "{}", err),
255      Self::InvalidLength(len) => write!(f, "{}", len),
256    }
257  }
258}
259
260impl core::error::Error for TryPutIntegerError {}
261
262#[cfg(feature = "std")]
263impl From<TryPutIntegerError> for std::io::Error {
264  fn from(value: TryPutIntegerError) -> Self {
265    std::io::Error::other(value)
266  }
267}
268/// Error type indicating an index or range is out of bounds.
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
270pub struct OutOfBounds {
271  /// The requested index or size
272  pub requested: usize,
273  /// The available length or capacity
274  pub available: usize,
275}
276
277impl OutOfBounds {
278  /// Creates a new `OutOfBounds` error.
279  #[inline]
280  pub const fn new(requested: usize, available: usize) -> Self {
281    Self {
282      requested,
283      available,
284    }
285  }
286}
287
288impl core::fmt::Display for OutOfBounds {
289  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
290    write!(
291      f,
292      "index out of bounds: requested {} but only {} available",
293      self.requested, self.available
294    )
295  }
296}
297
298impl core::error::Error for OutOfBounds {}
299
300#[cfg(feature = "std")]
301impl From<OutOfBounds> for std::io::Error {
302  fn from(value: OutOfBounds) -> Self {
303    std::io::Error::new(std::io::ErrorKind::InvalidInput, value)
304  }
305}
306
307/// Error type indicating an index or range is out of bounds.
308#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
309pub struct RangeOutOfBounds {
310  /// The start index of the range
311  pub start: usize,
312  /// The end index of the requested range
313  pub end: usize,
314  /// The available length or capacity
315  pub available: usize,
316}
317
318impl RangeOutOfBounds {
319  /// Creates a new `RangeOutOfBounds` error.
320  #[inline]
321  pub const fn new(start: usize, end: usize, available: usize) -> Self {
322    Self {
323      start,
324      end,
325      available,
326    }
327  }
328}
329
330impl core::fmt::Display for RangeOutOfBounds {
331  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
332    write!(
333      f,
334      "range out of bounds: requested {}..{} but only {} available",
335      self.start, self.end, self.available
336    )
337  }
338}
339
340impl core::error::Error for RangeOutOfBounds {}
341
342/// Error returned by raw byte editing operations.
343#[derive(Debug, PartialEq, Eq)]
344pub enum ByteEditError {
345  /// The requested byte range was invalid for the visible buffer contents.
346  Range(RangeOutOfBounds),
347  /// The replacement cannot fit in a fixed-capacity buffer.
348  Capacity(TryPutError),
349}
350
351impl core::fmt::Display for ByteEditError {
352  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
353    match self {
354      Self::Range(error) => error.fmt(f),
355      Self::Capacity(error) => error.fmt(f),
356    }
357  }
358}
359
360impl core::error::Error for ByteEditError {
361  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
362    match self {
363      Self::Range(error) => Some(error),
364      Self::Capacity(error) => Some(error),
365    }
366  }
367}
368
369impl From<RangeOutOfBounds> for ByteEditError {
370  fn from(value: RangeOutOfBounds) -> Self {
371    Self::Range(value)
372  }
373}
374
375impl From<TryPutError> for ByteEditError {
376  fn from(value: TryPutError) -> Self {
377    Self::Capacity(value)
378  }
379}
380
381/// Normalizes a range against `len` without panicking on reversed or
382/// overflowing bounds.
383pub(crate) fn normalize_range(
384  range: impl RangeBounds<usize>,
385  len: usize,
386) -> Result<(usize, usize), RangeOutOfBounds> {
387  let start = match range.start_bound() {
388    Bound::Included(&n) => n,
389    Bound::Excluded(&n) => n
390      .checked_add(1)
391      .ok_or_else(|| RangeOutOfBounds::new(n, n, len))?,
392    Bound::Unbounded => 0,
393  };
394
395  let end = match range.end_bound() {
396    Bound::Included(&n) => n
397      .checked_add(1)
398      .ok_or_else(|| RangeOutOfBounds::new(start, n, len))?,
399    Bound::Excluded(&n) => n,
400    Bound::Unbounded => len,
401  };
402
403  if start > end || end > len {
404    return Err(RangeOutOfBounds::new(start, end, len));
405  }
406
407  Ok((start, end))
408}
409
410#[cfg(feature = "std")]
411impl From<RangeOutOfBounds> for std::io::Error {
412  fn from(value: RangeOutOfBounds) -> Self {
413    std::io::Error::new(std::io::ErrorKind::InvalidInput, value)
414  }
415}
416
417/// Error type for UTF-8 index and range operations.
418#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
419pub enum Utf8IndexError {
420  /// The index does not lie on a UTF-8 character boundary.
421  InvalidCharBoundary {
422    /// The index that was not on a character boundary
423    index: usize,
424  },
425  /// A single index was outside the visible bounds.
426  IndexOutOfBounds(OutOfBounds),
427  /// A range was reversed, overflowing, or outside the visible bounds.
428  RangeOutOfBounds(RangeOutOfBounds),
429}
430
431impl core::fmt::Display for Utf8IndexError {
432  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
433    match self {
434      Self::InvalidCharBoundary { index } => {
435        write!(
436          f,
437          "index {} does not lie on a UTF-8 character boundary",
438          index
439        )
440      }
441      Self::IndexOutOfBounds(error) => error.fmt(f),
442      Self::RangeOutOfBounds(error) => error.fmt(f),
443    }
444  }
445}
446
447impl core::error::Error for Utf8IndexError {
448  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
449    match self {
450      Self::InvalidCharBoundary { .. } => None,
451      Self::IndexOutOfBounds(error) => Some(error),
452      Self::RangeOutOfBounds(error) => Some(error),
453    }
454  }
455}
456
457impl From<OutOfBounds> for Utf8IndexError {
458  fn from(value: OutOfBounds) -> Self {
459    Self::IndexOutOfBounds(value)
460  }
461}
462
463impl From<RangeOutOfBounds> for Utf8IndexError {
464  fn from(value: RangeOutOfBounds) -> Self {
465    Self::RangeOutOfBounds(value)
466  }
467}
468
469/// Error type for fixed-capacity UTF-8 editing operations.
470#[derive(Debug, PartialEq, Eq)]
471pub enum Utf8EditError {
472  /// The requested index or range is invalid.
473  Index(Utf8IndexError),
474  /// The replacement cannot fit in the fixed-capacity buffer.
475  Capacity(TryPutError),
476}
477
478impl core::fmt::Display for Utf8EditError {
479  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
480    match self {
481      Self::Index(error) => error.fmt(f),
482      Self::Capacity(error) => error.fmt(f),
483    }
484  }
485}
486
487impl core::error::Error for Utf8EditError {
488  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
489    match self {
490      Self::Index(error) => Some(error),
491      Self::Capacity(error) => Some(error),
492    }
493  }
494}
495
496impl From<Utf8IndexError> for Utf8EditError {
497  fn from(value: Utf8IndexError) -> Self {
498    Self::Index(value)
499  }
500}
501
502impl From<TryPutError> for Utf8EditError {
503  fn from(value: TryPutError) -> Self {
504    Self::Capacity(value)
505  }
506}
507
508/// Error returned by fallible formatting into a fixed UTF-8 buffer.
509#[derive(Debug, PartialEq, Eq)]
510pub enum Utf8FormatError {
511  /// Formatting exceeded the fixed buffer's remaining capacity.
512  Capacity(TryPutError),
513  /// The formatter returned an error unrelated to capacity.
514  Formatter(core::fmt::Error),
515}
516
517impl core::fmt::Display for Utf8FormatError {
518  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
519    match self {
520      Self::Capacity(error) => error.fmt(f),
521      Self::Formatter(error) => error.fmt(f),
522    }
523  }
524}
525
526impl core::error::Error for Utf8FormatError {
527  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
528    match self {
529      Self::Capacity(error) => Some(error),
530      Self::Formatter(error) => Some(error),
531    }
532  }
533}
534
535impl From<TryPutError> for Utf8FormatError {
536  fn from(value: TryPutError) -> Self {
537    Self::Capacity(value)
538  }
539}
540
541impl From<core::fmt::Error> for Utf8FormatError {
542  fn from(value: core::fmt::Error) -> Self {
543    Self::Formatter(value)
544  }
545}
546
547#[cfg(feature = "std")]
548impl From<Utf8IndexError> for std::io::Error {
549  fn from(value: Utf8IndexError) -> Self {
550    std::io::Error::new(std::io::ErrorKind::InvalidInput, value)
551  }
552}
553
554/// An owned byte container that failed UTF-8 validation.
555///
556/// The original bytes are retained so callers can inspect or recover the
557/// exact input without copying or changing its storage strategy.
558#[derive(Debug)]
559pub struct FromUtf8Error<B> {
560  bytes: B,
561  error: core::str::Utf8Error,
562}
563
564impl<B> FromUtf8Error<B> {
565  #[inline]
566  pub(crate) const fn new(bytes: B, error: core::str::Utf8Error) -> Self {
567    Self { bytes, error }
568  }
569
570  /// Returns the UTF-8 validation error.
571  #[inline]
572  pub const fn utf8_error(&self) -> core::str::Utf8Error {
573    self.error
574  }
575
576  /// Recovers the original bytes.
577  #[inline]
578  pub fn into_bytes(self) -> B {
579    self.bytes
580  }
581}
582
583impl<B> FromUtf8Error<B>
584where
585  B: AsRef<[u8]>,
586{
587  /// Returns the original bytes without consuming the error.
588  #[inline]
589  pub fn as_bytes(&self) -> &[u8] {
590    self.bytes.as_ref()
591  }
592}
593
594impl<B> core::fmt::Display for FromUtf8Error<B> {
595  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
596    write!(f, "invalid UTF-8: {}", self.error)
597  }
598}
599
600impl<B: core::fmt::Debug> core::error::Error for FromUtf8Error<B> {
601  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
602    Some(&self.error)
603  }
604}
605
606#[cfg(feature = "std")]
607impl<B: core::fmt::Debug + Send + Sync + 'static> From<FromUtf8Error<B>> for std::io::Error {
608  fn from(value: FromUtf8Error<B>) -> Self {
609    std::io::Error::new(std::io::ErrorKind::InvalidData, value)
610  }
611}
612
613/// Error type for converting a raw byte slice into a fixed-capacity
614/// UTF-8 buffer. Combines UTF-8 validation and capacity errors.
615#[derive(Debug, PartialEq, Eq)]
616pub enum FromBytesError {
617  /// The bytes are not valid UTF-8.
618  InvalidUtf8(core::str::Utf8Error),
619  /// The bytes do not fit in the buffer's capacity.
620  TooLarge(TryPutError),
621}
622
623impl core::fmt::Display for FromBytesError {
624  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
625    match self {
626      Self::InvalidUtf8(e) => write!(f, "invalid UTF-8: {}", e),
627      Self::TooLarge(e) => write!(f, "{}", e),
628    }
629  }
630}
631
632impl core::error::Error for FromBytesError {}
633
634impl From<core::str::Utf8Error> for FromBytesError {
635  fn from(e: core::str::Utf8Error) -> Self {
636    Self::InvalidUtf8(e)
637  }
638}
639
640impl From<TryPutError> for FromBytesError {
641  fn from(e: TryPutError) -> Self {
642    Self::TooLarge(e)
643  }
644}
645
646#[cfg(feature = "std")]
647impl From<FromBytesError> for std::io::Error {
648  fn from(value: FromBytesError) -> Self {
649    std::io::Error::new(std::io::ErrorKind::InvalidInput, value)
650  }
651}