bencode2json/parsers/
error.rs

1//! Parser errors.
2use core::str;
3use std::{
4    fmt::{self},
5    io,
6};
7
8use thiserror::Error;
9
10use crate::rw;
11
12use super::BencodeType;
13
14/// Errors that can occur while parsing a bencoded value.
15#[derive(Debug, Error)]
16pub enum Error {
17    /// I/O error.
18    #[error("I/O error: {0}")]
19    Io(#[from] io::Error),
20
21    /// R/W error.
22    #[error("R/W error: {0}")]
23    Rw(#[from] rw::error::Error),
24
25    /// Read byte after peeking does match peeked byte.
26    ///
27    /// The main parser peeks one byte ahead to know what kind of bencoded value
28    /// is being parsed. If the byte read after peeking does not match the
29    /// peeked byte, it means the input is being consumed somewhere else.
30    #[error("Read byte after peeking does match peeked byte; {0}; {1}")]
31    ReadByteAfterPeekingDoesMatchPeekedByte(ReadContext, WriteContext),
32
33    /// Unrecognized first byte for new bencoded value.
34    ///
35    /// The main parser peeks one byte ahead to know what kind of bencoded value
36    /// is being parsed. This error is raised when the peeked byte is not a
37    /// valid first byte for a bencoded value.
38    #[error("Unrecognized first byte for new bencoded value; {0}; {1}")]
39    UnrecognizedFirstBencodeValueByte(ReadContext, WriteContext),
40
41    // Integers
42    /// Unexpected byte parsing integer.
43    ///
44    /// The main parser parses integers by reading bytes until it finds the
45    /// end of the integer. This error is raised when the byte read is not a
46    /// valid byte for an integer bencoded value.
47    #[error("Unexpected byte parsing integer; {0}; {1}")]
48    UnexpectedByteParsingInteger(ReadContext, WriteContext),
49
50    /// Unexpected end of input parsing integer.
51    ///
52    /// The input ends before the integer ends.
53    #[error("Unexpected end of input parsing integer; {0}; {1}")]
54    UnexpectedEndOfInputParsingInteger(ReadContext, WriteContext),
55
56    /// Leading zeros in integers are not allowed, for example b'i00e'.
57    #[error("Leading zeros in integers are not allowed, for example b'i00e'; {0}; {1}")]
58    LeadingZerosInIntegersNotAllowed(ReadContext, WriteContext),
59
60    // Strings
61    /// Invalid string length byte, expected a digit.
62    ///
63    /// The string parser found an invalid byte for the string length. The
64    /// length can only be made of digits (0-9).
65    #[error("Invalid string length byte, expected a digit; {0}; {1}")]
66    InvalidStringLengthByte(ReadContext, WriteContext),
67
68    /// Unexpected end of input parsing string length.
69    ///
70    /// The input ends before the string length ends.
71    #[error("Unexpected end of input parsing string length; {0}; {1}")]
72    UnexpectedEndOfInputParsingStringLength(ReadContext, WriteContext),
73
74    /// Unexpected end of input parsing string value.
75    ///
76    /// The input ends before the string value ends.
77    #[error("Unexpected end of input parsing string value; {0}; {1}")]
78    UnexpectedEndOfInputParsingStringValue(ReadContext, WriteContext),
79
80    // Lists
81    /// Unexpected end of input parsing list. Expecting first list item or list end.
82    #[error(
83        "Unexpected end of input parsing list. Expecting first list item or list end; {0}; {1}"
84    )]
85    UnexpectedEndOfInputExpectingFirstListItemOrEnd(ReadContext, WriteContext),
86
87    /// Unexpected end of input parsing list. Expecting next list item.
88    #[error("Unexpected end of input parsing list. Expecting next list item; {0}; {1}")]
89    UnexpectedEndOfInputExpectingNextListItem(ReadContext, WriteContext),
90
91    // Dictionaries
92    /// Unexpected end of input parsing dictionary. Expecting first dictionary field or dictionary end.
93    #[error("Unexpected end of input parsing dictionary. Expecting first dictionary field or dictionary end; {0}; {1}")]
94    UnexpectedEndOfInputExpectingFirstDictFieldOrEnd(ReadContext, WriteContext),
95
96    /// Unexpected end of input parsing dictionary. Expecting dictionary field value.
97    #[error(
98        "Unexpected end of input parsing dictionary. Expecting dictionary field value; {0}; {1}"
99    )]
100    UnexpectedEndOfInputExpectingDictFieldValue(ReadContext, WriteContext),
101
102    /// Unexpected end of input parsing dictionary. Expecting dictionary field key or end.
103    #[error(
104        "Unexpected end of input parsing dictionary. Expecting dictionary field key or end; {0}; {1}"
105    )]
106    UnexpectedEndOfInputExpectingDictFieldKeyOrEnd(ReadContext, WriteContext),
107
108    /// Unexpected end of dictionary. Premature end of dictionary.
109    #[error("Unexpected end of dictionary. Premature end of dictionary; {0}; {1}")]
110    PrematureEndOfDict(ReadContext, WriteContext),
111
112    /// Expected string for dictionary field key.
113    #[error("Expected string for dictionary field key, but got: {0}, {1}")]
114    ExpectedStringForDictKeyGot(BencodeType, ReadContext, WriteContext),
115
116    // List and dictionaries
117    /// Unexpected end of list or dict. No matching start for the list or dict end.
118    #[error(
119        "Unexpected end of list or dict. No matching start for the list or dict end: {0}, {1}"
120    )]
121    NoMatchingStartForListOrDictEnd(ReadContext, WriteContext),
122}
123
124/// The reader context when the error ocurred.
125#[derive(Debug)]
126pub struct ReadContext {
127    /// The read byte that caused the error if any.
128    pub byte: Option<u8>,
129
130    /// The position of the read byte that caused the error.
131    pub pos: u64,
132
133    /// The latest bytes read from input.
134    pub latest_bytes: Vec<u8>,
135}
136
137impl fmt::Display for ReadContext {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        write!(f, "read context:")?;
140
141        match self.byte {
142            None => {}
143            Some(byte) => write!(f, " byte `{}` (char: `{}`),", byte, byte as char)?,
144        }
145
146        write!(
147            f,
148            " input pos {}, latest input bytes dump: {:?}",
149            self.pos, self.latest_bytes
150        )?;
151
152        if let Ok(utf8_string) = str::from_utf8(&self.latest_bytes) {
153            write!(f, " (UTF-8 string: `{utf8_string}`)")?;
154        }
155
156        Ok(())
157    }
158}
159
160/// The writer context when the error ocurred.
161#[derive(Debug)]
162pub struct WriteContext {
163    /// The written byte that caused the error if any.
164    pub byte: Option<u8>,
165
166    /// The position of the written byte that caused the error.
167    pub pos: u64,
168
169    /// The latest bytes written to the output.
170    pub latest_bytes: Vec<u8>,
171}
172
173impl fmt::Display for WriteContext {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        write!(f, "write context:")?;
176
177        match self.byte {
178            None => {}
179            Some(byte) => write!(f, " byte `{}` (char: `{}`),", byte, byte as char)?,
180        }
181
182        write!(
183            f,
184            " output pos {}, latest output bytes dump: {:?}",
185            self.pos, self.latest_bytes
186        )?;
187
188        if let Ok(utf8_string) = str::from_utf8(&self.latest_bytes) {
189            write!(f, " (UTF-8 string: `{utf8_string}`)")?;
190        }
191
192        Ok(())
193    }
194}
195
196#[cfg(test)]
197mod tests {
198
199    mod for_read_context {
200        use crate::parsers::error::ReadContext;
201
202        #[test]
203        fn it_should_display_the_read_context() {
204            let read_context = ReadContext {
205                byte: Some(b'a'),
206                pos: 10,
207                latest_bytes: vec![b'a', b'b', b'c'],
208            };
209
210            assert_eq!( read_context.to_string(),"read context: byte `97` (char: `a`), input pos 10, latest input bytes dump: [97, 98, 99] (UTF-8 string: `abc`)");
211        }
212
213        #[test]
214        fn it_should_not_display_the_byte_if_it_is_none() {
215            let read_context = ReadContext {
216                byte: None,
217                pos: 10,
218                latest_bytes: vec![b'a', b'b', b'c'],
219            };
220
221            assert_eq!(read_context.to_string(), "read context: input pos 10, latest input bytes dump: [97, 98, 99] (UTF-8 string: `abc`)");
222        }
223
224        #[test]
225        fn it_should_not_display_the_latest_bytes_as_string_if_it_is_not_a_valid_string() {
226            let read_context = ReadContext {
227                byte: None,
228                pos: 10,
229                latest_bytes: vec![b'\xFF', b'\xFE'],
230            };
231
232            assert_eq!(
233                read_context.to_string(),
234                "read context: input pos 10, latest input bytes dump: [255, 254]"
235            );
236        }
237    }
238
239    mod for_write_context {
240        use crate::parsers::error::WriteContext;
241
242        #[test]
243        fn it_should_display_the_read_context() {
244            let read_context = WriteContext {
245                byte: Some(b'a'),
246                pos: 10,
247                latest_bytes: vec![b'a', b'b', b'c'],
248            };
249
250            assert_eq!( read_context.to_string(),"write context: byte `97` (char: `a`), output pos 10, latest output bytes dump: [97, 98, 99] (UTF-8 string: `abc`)");
251        }
252
253        #[test]
254        fn it_should_not_display_the_byte_if_it_is_none() {
255            let read_context = WriteContext {
256                byte: None,
257                pos: 10,
258                latest_bytes: vec![b'a', b'b', b'c'],
259            };
260
261            assert_eq!(read_context.to_string(), "write context: output pos 10, latest output bytes dump: [97, 98, 99] (UTF-8 string: `abc`)");
262        }
263
264        #[test]
265        fn it_should_not_display_the_latest_bytes_as_string_if_it_is_not_a_valid_string() {
266            let read_context = WriteContext {
267                byte: None,
268                pos: 10,
269                latest_bytes: vec![b'\xFF', b'\xFE'],
270            };
271
272            assert_eq!(
273                read_context.to_string(),
274                "write context: output pos 10, latest output bytes dump: [255, 254]"
275            );
276        }
277    }
278}