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}