Skip to main content

slice_codec/
error.rs

1// Copyright (c) ZeroC, Inc.
2
3use core::fmt::{Display, Formatter};
4use core::num::TryFromIntError;
5use core::ops::Range;
6use core::write;
7
8#[cfg(feature = "alloc")]
9use core::marker::{Send, Sync};
10
11#[cfg(feature = "alloc")]
12use alloc::boxed::Box;
13#[cfg(feature = "alloc")]
14use alloc::collections::TryReserveError;
15#[cfg(feature = "alloc")]
16use alloc::string::FromUtf8Error;
17
18/// A specialized [`Result`](core::result::Result) type for encoding and decoding functions which may produce errors.
19///
20/// This typedef is a convenience to avoid repetitively specifying [`Error`] as the error type, and is a direct mapping
21/// to a [`core::result::Result`] with an `Err` type of [`Error`].
22pub type Result<T> = core::result::Result<T, Error>;
23
24/// The error type for encoding and decoding functions.
25#[derive(Debug)]
26pub struct Error {
27    /// Describes the kind of error that occurred and provides additional information about it.
28    kind: ErrorKind,
29
30    /// The underlying cause of this error, if any exist.
31    #[cfg(feature = "alloc")]
32    source: Option<Box<dyn core::error::Error + Send + Sync + 'static>>,
33}
34
35impl Error {
36    /// Creates a new error of the specified kind, with no underlying source.
37    pub fn new(kind: ErrorKind) -> Self {
38        Self {
39            kind,
40            #[cfg(feature = "alloc")]
41            source: None,
42        }
43    }
44
45    /// Creates a new error of the specified kind, which was logically caused by the provided source.
46    #[cfg(feature = "alloc")]
47    pub fn new_with_source(kind: ErrorKind, source: impl core::error::Error + Send + Sync + 'static) -> Self {
48        Self {
49            kind,
50            source: Some(Box::new(source)),
51        }
52    }
53
54    /// Returns the corresponding [`ErrorKind`] that describes this error.
55    pub fn kind(&self) -> &ErrorKind {
56        &self.kind
57    }
58}
59
60impl Display for Error {
61    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
62        // Write this error's underlying `ErrorKind`.
63        self.kind.fmt(f)?;
64
65        #[cfg(feature = "alloc")]
66        // If this error was caused by another error, also write that source error.
67        if let Some(source) = &self.source {
68            f.write_str("\nError was caused by:\n")?;
69            source.fmt(f)?;
70        }
71
72        Ok(())
73    }
74}
75
76#[cfg(feature = "alloc")]
77impl core::error::Error for Error {
78    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
79        // TODO, this should coerce automatically: see https://github.com/rust-lang/rust/issues/88001.
80        self.source.as_deref().map(|e| e as &(dyn core::error::Error + 'static))
81    }
82}
83
84impl<T: Into<ErrorKind>> From<T> for Error {
85    /// Creates a new [`Error`] from the provided [`ErrorKind`], with no underlying source.
86    fn from(value: T) -> Self {
87        Self::new(value.into())
88    }
89}
90
91/// A list that specifies all the kinds of errors that can be returned by this crate's functions.
92/// It is typically held by an [`Error`].
93///
94/// This list may grow over time, so it is not recommended to exhaustively match against it.
95#[derive(Debug)]
96#[non_exhaustive]
97pub enum ErrorKind {
98    /// A function attempted to read past the end of a buffer.
99    UnexpectedEob {
100        /// The number of bytes that the function tried to read.
101        requested: usize,
102        /// The number of readable bytes that were left in the buffer.
103        remaining: usize,
104    },
105
106    /// A buffer reservation did not fit within its buffer.
107    /// This error represents a serious problem in the implementation, or intentional tampering by callers.
108    /// See [`write_bytes_exact_into_reserved`](crate::buffer::OutputTarget::write_bytes_into_reserved_exact).
109    InvalidReservation {
110        /// The length of the buffer.
111        buffer_len: usize,
112        /// The range (pair of indices) in the buffer which were reserved invalidly.
113        reserved_range: Range<usize>,
114    },
115
116    /// The system failed to allocate memory.
117    /// Unlike [`ErrorKind::AllocationLimitReached`],
118    ///
119    /// Most probably, this happened when a decoder attempted to allocate space for a string or collection.
120    #[cfg(feature = "alloc")]
121    AllocationError(TryReserveError),
122
123    /// Hello
124    AllocationLimitReached {
125        requested: usize,
126
127        remaining: usize,
128    },
129
130    InvalidData(InvalidDataErrorKind),
131}
132
133impl Display for ErrorKind {
134    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
135        match self {
136            Self::UnexpectedEob { requested, remaining } => {
137                write!(f, "unexpected end of buffer: attempted to read '{requested}' bytes from a buffer with only '{remaining}' bytes remaining")
138            }
139            Self::InvalidReservation {
140                buffer_len,
141                reserved_range,
142            } => {
143                let Range { start, end } = reserved_range;
144                write!(
145                    f,
146                    "invalid reservation: range '[{start}..{end})' does not fit within buffer of length '{buffer_len}'"
147                )
148            }
149            Self::InvalidData(inner) => inner.fmt(f),
150            _ => todo!(),
151        }
152    }
153}
154
155#[derive(Debug)]
156#[non_exhaustive]
157pub enum InvalidDataErrorKind {
158    /// TODO
159    IllegalValue { desc: &'static str, value: Option<i128> },
160
161    /// TODO
162    /// A malformed string (one whose bytes aren't valid UTF8) was encountered.
163    #[cfg(feature = "alloc")]
164    InvalidString(FromUtf8Error),
165
166    /// TODO
167    OutOfRange {
168        value: i128,
169        min: i128,
170        max: i128,
171        typename: &'static str,
172    },
173
174    /// A key appears multiple times in a dictionary, violating the uniqueness requirement.
175    #[cfg(feature = "alloc")]
176    DuplicateDictionaryKey,
177}
178
179impl Display for InvalidDataErrorKind {
180    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
181        match self {
182            Self::IllegalValue { desc, value } => {
183                if let Some(value) = value {
184                    write!(f, "illegal value: {desc} (value: {value})")
185                } else {
186                    write!(f, "illegal value: {desc}")
187                }
188            }
189
190            #[cfg(feature = "alloc")]
191            Self::InvalidString(inner) => inner.fmt(f),
192
193            Self::OutOfRange { value, min, max, typename } => {
194                write!(
195                    f,
196                    "value '{value}' is outside the allowed range for type '{typename}'; values must be within [{min}..{max}]"
197                )
198            }
199
200            #[cfg(feature = "alloc")]
201            Self::DuplicateDictionaryKey => write!(f, "duplicate dictionary key encountered"),
202        }
203    }
204}
205
206impl From<InvalidDataErrorKind> for ErrorKind {
207    fn from(value: InvalidDataErrorKind) -> Self {
208        ErrorKind::InvalidData(value)
209    }
210}
211
212impl From<TryFromIntError> for Error {
213    fn from(_: TryFromIntError) -> Self {
214        Error::from(InvalidDataErrorKind::IllegalValue {
215            desc: "failed to convert integer",
216            value: None,
217        })
218    }
219}
220
221#[cfg(feature = "alloc")]
222impl From<TryReserveError> for Error {
223    fn from(value: TryReserveError) -> Self {
224        Error::from(ErrorKind::AllocationError(value))
225    }
226}
227
228#[cfg(feature = "alloc")]
229impl From<FromUtf8Error> for Error {
230    fn from(value: FromUtf8Error) -> Self {
231        Error::from(InvalidDataErrorKind::InvalidString(value))
232    }
233}
234
235#[cfg(feature = "std")]
236impl From<Error> for std::io::Error {
237    fn from(value: Error) -> Self {
238        std::io::Error::other(value)
239    }
240}