Skip to main content

zalgo_codec_common/
error.rs

1//! Contains the definition of the error type used by the encoding functions in the crate.
2
3use core::fmt;
4
5#[cfg(feature = "std")]
6use std::backtrace::Backtrace;
7
8use alloc::string::FromUtf8Error;
9
10#[derive(Debug)]
11/// The error returned by [`zalgo_encode`](crate::zalgo_encode), [`ZalgoString::try_from`](crate::ZalgoString::try_from), and [`zalgo_wrap_python`](crate::zalgo_wrap_python)
12/// if they encounter a byte they can not encode.
13pub struct EncodeError {
14    unencodable_character: char,
15    line: usize,
16    column: usize,
17    index: usize,
18    #[cfg(feature = "std")]
19    backtrace: Backtrace,
20}
21
22impl EncodeError {
23    /// Creates a new `Error`.
24    ///
25    /// # Note
26    ///
27    /// This associated method does not check the validity of its inputs,
28    /// and just constructs a new `Error` instance.
29    #[inline]
30    #[must_use = "this associated method does not modify its inputs and just returns a new value"]
31    pub(crate) fn new(
32        unencodable_character: char,
33        line: usize,
34        column: usize,
35        index: usize,
36    ) -> Self {
37        Self {
38            unencodable_character,
39            line,
40            column,
41            index,
42            #[cfg(feature = "std")]
43            backtrace: Backtrace::capture(),
44        }
45    }
46
47    /// Returns the 1-indexed line number of the line on which the unencodable byte occured.
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// # use zalgo_codec_common::{EncodeError, zalgo_encode};
53    /// assert_eq!(zalgo_encode("❤️").map_err(|e| e.line()), Err(1));
54    /// assert_eq!(zalgo_encode("a\nb\nc\r\n").map_err(|e| e.line()), Err(3));
55    /// ```
56    #[inline]
57    #[must_use = "the method returns a new value and does not modify `self`"]
58    pub const fn line(&self) -> usize {
59        self.line
60    }
61
62    /// Returns the 1-indexed column where the unencodable byte occured.
63    /// Columns are counted from left to right and the count resets for each new line.
64    ///
65    /// # Example
66    ///
67    /// ```
68    /// # use zalgo_codec_common::{EncodeError, zalgo_encode};
69    /// assert_eq!(zalgo_encode("I ❤️ 🎂").map_err(|e| e.column()), Err(3));
70    /// assert_eq!(zalgo_encode("I\n❤️\n🎂").map_err(|e| e.column()), Err(1));
71    /// ```
72    #[inline]
73    #[must_use = "the method returns a new value and does not modify `self`"]
74    pub const fn column(&self) -> usize {
75        self.column
76    }
77
78    /// Returns the unencodable character that caused the error.
79    ///
80    /// This may not match with what you see when you look at the unencoded string in a text editor since
81    /// some grapheme clusters consist of many unicode characters.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// # use zalgo_codec_common::zalgo_encode;
87    /// assert_eq!(zalgo_encode("CRLF\r\n").map_err(|e| e.char()), Err('\r'));
88    /// ```  
89    /// The ❤️ emoji consists of two characters, the heart `U+2764` and the color variant selector `U+FE0F`.
90    /// Since the heart is not encodable, that is the place where the error is generated:
91    /// ```
92    /// # use zalgo_codec_common::zalgo_encode;
93    /// assert_eq!(zalgo_encode("❤️").map_err(|e| e.char()), Err('❤'));
94    /// ```
95    /// The grapheme cluster `á` consists of a normal `a` and a combining acute accent, `U+301`.
96    /// The `a` can be encoded and the combining acute accent can not, so the error points only to the accent:
97    /// ```
98    /// # use zalgo_codec_common::zalgo_encode;
99    /// assert_eq!(zalgo_encode("á").map_err(|e| e.char()), Err('\u{301}'))
100    /// ```
101    #[inline]
102    #[must_use = "the method returns a new value and does not modify `self`"]
103    pub const fn char(&self) -> char {
104        self.unencodable_character
105    }
106
107    /// Returns the index of the string where the unencodable character occured.
108    ///
109    /// # Example
110    ///
111    /// ```
112    /// # use zalgo_codec_common::zalgo_encode;
113    /// assert_eq!(zalgo_encode("ab\ncdë").map_err(|e| e.index()), Err(5));
114    /// ```
115    #[inline]
116    #[must_use = "the method returns a new value and does not modify `self`"]
117    pub const fn index(&self) -> usize {
118        self.index
119    }
120
121    #[cfg(feature = "std")]
122    /// Returns a reference to a [`Backtrace`] that was captured when the error was created.
123    ///
124    /// See the documentation of [`Backtrace::capture`] for more information about how to make it
125    /// show more information when displayed.
126    #[inline]
127    pub fn backtrace(&self) -> &Backtrace {
128        &self.backtrace
129    }
130}
131
132impl fmt::Display for EncodeError {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        write!(
135            f,
136            "can not encode {:?} character at string index {}, on line {} at column {}",
137            self.char(),
138            self.index(),
139            self.line(),
140            self.column(),
141        )
142    }
143}
144
145impl core::error::Error for EncodeError {}
146
147/// The error returned by [`zalgo_decode`](super::zalgo_decode) if a string can not be decoded.
148#[derive(Debug)]
149pub struct DecodeError {
150    source_error: FromUtf8Error,
151    #[cfg(feature = "std")]
152    backtrace: Backtrace,
153}
154
155impl DecodeError {
156    pub(crate) fn new(source_error: FromUtf8Error) -> Self {
157        Self {
158            #[cfg(feature = "std")]
159            backtrace: Backtrace::capture(),
160            source_error,
161        }
162    }
163
164    #[cfg(feature = "std")]
165    /// Returns a backtrace to where the error was created.
166    ///
167    /// The backtrace was captured with [`Backtrace::capture`], see it for more information
168    /// on how to make it show information when printed.
169    pub fn backtrace(&self) -> &Backtrace {
170        &self.backtrace
171    }
172
173    /// This function converts this error into the [`FromUtf8Error`] that caused this decoding error.
174    pub fn into_from_utf8_error(self) -> FromUtf8Error {
175        self.source_error
176    }
177}
178
179impl fmt::Display for DecodeError {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(
182            f,
183            "could not decode the string because {}",
184            self.source_error
185        )
186    }
187}
188
189impl core::error::Error for DecodeError {
190    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
191        Some(&self.source_error)
192    }
193}
194
195#[cfg(test)]
196mod test {
197    use super::{DecodeError, EncodeError};
198    use alloc::{string::String, vec};
199
200    #[test]
201    fn test_error() {
202        let err = EncodeError::new('å', 1, 7, 6);
203        assert_eq!(err.char(), 'å');
204        assert_eq!(err.line(), 1);
205        assert_eq!(err.column(), 7);
206        assert_eq!(err.index(), 6);
207    }
208
209    #[test]
210    fn test_decode_error() {
211        let err =
212            DecodeError::new(String::from_utf8(vec![255, 255, 255, 255, 255, 255]).unwrap_err());
213        assert_eq!(err.into_from_utf8_error().into_bytes(), vec![255; 6]);
214    }
215}