Mercurial > core / rust/lib/sxp/src/err.rs
changeset 698: |
96958d3eb5b0 |
parent: |
3d78bed56188
|
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Fri, 04 Oct 2024 22:04:59 -0400 |
permissions: |
-rw-r--r-- |
description: |
fixes |
1 //! err.rs --- When serializing or deserializing SXP goes wrong... 6 string::{String, ToString}, 9 fmt::{self, Debug, Display}, 14 #[cfg(feature = "std")] 16 #[cfg(feature = "std")] 17 use std::io::ErrorKind; 19 pub type Result<T> = result::Result<T, Error>; 21 /// This type represents all possible errors that can occur when serializing or 22 /// deserializing SXP data. 24 /// This `Box` allows us to keep the size of `Error` as small as possible. A 25 /// larger `Error` type was substantially slower due to all the functions 26 /// that pass around `Result<T, Error>`. 31 /// One-based line number at which the error was detected. 33 /// Characters in the first line of the input (before the first newline 34 /// character) are in line 1. 35 pub fn line(&self) -> usize { 39 // TODO 2023-07-14: I kinda want to change this to Zero-based, as 40 // I'm pretty sure that's the default functionality of emacs (with 41 // cursor at '(point-min)' the position is '(:line 1 :col 0)'.. 42 /// One-based column number at which the error was detected. 44 /// The first character in the input and any characters immediately 45 /// following a newline character are in column 1. 47 /// Note that errors may occur in column 0, for example if a read from an 48 /// I/O stream fails immediately following a previously read newline 50 pub fn column(&self) -> usize { 54 /// Categorizes the cause of this error. 56 /// - `Category::Io` - failure to read or write bytes on an I/O stream 57 /// - `Category::Syntax` - input that is not syntactically valid SXP 58 /// - `Category::Data` - input data that is semantically incorrect 59 /// - `Category::Eof` - unexpected end of the input data 60 pub fn classify(&self) -> Category { 62 ErrorCode::Message(_) => Category::Data, 63 ErrorCode::Io(_) => Category::Io, 64 ErrorCode::EofWhileParsingList 65 | ErrorCode::EofWhileParsingObject 66 | ErrorCode::EofWhileParsingString 67 | ErrorCode::EofWhileParsingValue => Category::Eof, 68 ErrorCode::ExpectedListEnd 69 | ErrorCode::ExpectedSomeSymbol 70 | ErrorCode::ExpectedSomeValue 71 | ErrorCode::ExpectedDoubleQuote 72 | ErrorCode::InvalidEscape 73 | ErrorCode::InvalidNumber 74 | ErrorCode::NumberOutOfRange 75 | ErrorCode::InvalidUnicodeCodePoint 76 | ErrorCode::ControlCharacterWhileParsingString 77 | ErrorCode::KeyMustBeASymbol 78 | ErrorCode::LoneLeadingSurrogateInHexEscape 79 | ErrorCode::TrailingCharacters 80 | ErrorCode::UnexpectedEndOfHexEscape 81 | ErrorCode::RecursionLimitExceeded => Category::Syntax, 85 /// Returns true if this error was caused by a failure to read or write 86 /// bytes on an I/O stream. 87 pub fn is_io(&self) -> bool { 88 self.classify() == Category::Io 91 /// Returns true if this error was caused by input that was not 92 /// syntactically valid SXP. 93 pub fn is_syntax(&self) -> bool { 94 self.classify() == Category::Syntax 97 /// Returns true if this error was caused by input data that was 98 /// semantically incorrect. 100 /// For example, SXP containing a number is semantically incorrect when the 101 /// type being deserialized into holds a String. 102 pub fn is_data(&self) -> bool { 103 self.classify() == Category::Data 106 /// Returns true if this error was caused by prematurely reaching the end of 109 /// Callers that process streaming input may be interested in retrying the 110 /// deserialization once more data is available. 111 pub fn is_eof(&self) -> bool { 112 self.classify() == Category::Eof 115 /// The kind reported by the underlying standard library I/O error, if this 116 /// error was caused by a failure to read or write bytes on an I/O stream. 117 #[cfg(feature = "std")] 118 pub fn io_error_kind(&self) -> Option<ErrorKind> { 119 if let ErrorCode::Io(io_error) = &self.err.code { 120 Some(io_error.kind()) 127 /// Categorizes the cause of a `sxp::Error`. 128 #[derive(Copy, Clone, PartialEq, Eq, Debug)] 130 /// The error was caused by a failure to read or write bytes on an I/O 134 /// The error was caused by input that was not syntactically valid SXP. 137 /// The error was caused by input data that was semantically incorrect. 139 /// For example, SXP containing a number is semantically incorrect when the 140 /// type being deserialized into holds a String. 143 /// The error was caused by prematurely reaching the end of the input data. 145 /// Callers that process streaming input may be interested in retrying the 146 /// deserialization once more data is available. 150 #[cfg(feature = "std")] 151 #[allow(clippy::fallible_impl_from)] 152 impl From<Error> for io::Error { 153 /// Convert a `sxp::Error` into an `io::Error`. 155 /// SXP syntax and data errors are turned into `InvalidData` I/O errors. 156 /// EOF errors are turned into `UnexpectedEof` I/O errors. 157 fn from(j: Error) -> Self { 158 if let ErrorCode::Io(err) = j.err.code { 162 Category::Io => unreachable!(), 163 Category::Syntax | Category::Data => { 164 io::Error::new(ErrorKind::InvalidData, j) 166 Category::Eof => io::Error::new(ErrorKind::UnexpectedEof, j), 179 pub(crate) enum ErrorCode { 180 /// Catchall for syntax error messages 182 /// Some I/O error occurred while serializing or deserializing. 184 /// EOF while parsing a list. 186 /// EOF while parsing an object. 187 EofWhileParsingObject, 188 /// EOF while parsing a string. 189 EofWhileParsingString, 190 /// EOF while parsing a SXP value. 191 EofWhileParsingValue, 192 /// Expected this character to be ')'. 194 /// Expected to parse a symbol. 196 /// Expected this character to start a SXP value. 198 /// Expected this character to be '"'. 200 /// Invalid hex escape code. 204 /// Number is bigger than the maximum value of its type. 206 /// Invalid unicode code point. 207 InvalidUnicodeCodePoint, 208 /// Control character found while parsing a string. 209 ControlCharacterWhileParsingString, 210 /// Object key is not a symbol. 212 /// Lone leading surrogate in hex escape. 213 LoneLeadingSurrogateInHexEscape, 214 /// SXP has non-whitespace trailing characters after the value. 216 /// Unexpected end of hex escape. 217 UnexpectedEndOfHexEscape, 218 /// Encountered nesting of SXP maps and arrays more than 128 layers deep. 219 RecursionLimitExceeded, 224 pub(crate) fn syntax(code: ErrorCode, line: usize, column: usize) -> Self { 226 err: Box::new(ErrorImpl { code, line, column }), 232 pub fn io(error: io::Error) -> Self { 234 err: Box::new(ErrorImpl { 235 code: ErrorCode::Io(error), 243 // pub(crate) fn fix_position<F>(self, f: F) -> Self 245 // F: FnOnce(ErrorCode) -> Error, 247 // if self.err.line == 0 { 255 impl Display for ErrorCode { 256 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 258 ErrorCode::Message(msg) => f.write_str(msg), 259 ErrorCode::Io(err) => Display::fmt(err, f), 260 ErrorCode::EofWhileParsingList => f.write_str("EOF while parsing a list"), 261 ErrorCode::EofWhileParsingObject => { 262 f.write_str("EOF while parsing an object") 264 ErrorCode::EofWhileParsingString => { 265 f.write_str("EOF while parsing a string") 267 ErrorCode::EofWhileParsingValue => { 268 f.write_str("EOF while parsing a value") 270 ErrorCode::ExpectedListEnd => f.write_str("expected `)`"), 271 ErrorCode::ExpectedSomeSymbol => f.write_str("expected symbol"), 272 ErrorCode::ExpectedSomeValue => f.write_str("expected value"), 273 ErrorCode::ExpectedDoubleQuote => f.write_str("expected `\"`"), 274 ErrorCode::InvalidEscape => f.write_str("invalid escape"), 275 ErrorCode::InvalidNumber => f.write_str("invalid number"), 276 ErrorCode::NumberOutOfRange => f.write_str("number out of range"), 277 ErrorCode::InvalidUnicodeCodePoint => { 278 f.write_str("invalid unicode code point") 280 ErrorCode::ControlCharacterWhileParsingString => f.write_str( 281 "control character (\\u0000-\\u001F) found while parsing a string", 283 ErrorCode::KeyMustBeASymbol => f.write_str("key must be a symbol"), 284 ErrorCode::LoneLeadingSurrogateInHexEscape => { 285 f.write_str("lone leading surrogate in hex escape") 287 ErrorCode::TrailingCharacters => f.write_str("trailing characters"), 288 ErrorCode::UnexpectedEndOfHexEscape => { 289 f.write_str("unexpected end of hex escape") 291 ErrorCode::RecursionLimitExceeded => { 292 f.write_str("recursion limit exceeded") 298 impl serde::de::StdError for Error { 299 #[cfg(feature = "std")] 300 fn source(&self) -> Option<&(dyn error::Error + 'static)> { 301 match &self.err.code { 302 ErrorCode::Io(err) => err.source(), 308 impl Display for Error { 309 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 310 Display::fmt(&*self.err, f) 314 impl Display for ErrorImpl { 315 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 317 Display::fmt(&self.code, f) 321 "{} at line {} column {}", 322 self.code, self.line, self.column 328 // Remove two layers of verbosity from the debug representation. Humans often 329 // end up seeing this representation because it is what unwrap() shows. 330 impl Debug for Error { 331 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 334 "(error {:?} (:line {} :column {}))", 335 self.err.code.to_string(), 342 impl de::Error for Error { 344 fn custom<T: Display>(msg: T) -> Error { 345 make_error(msg.to_string()) 349 fn invalid_type(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self { 350 if let de::Unexpected::Unit = unexp { 351 Error::custom(format_args!("(:invalid-type nil :expected {})", exp)) 353 Error::custom(format_args!("(:invalid-type {} :expected {})", unexp, exp)) 358 impl ser::Error for Error { 360 fn custom<T: Display>(msg: T) -> Error { 361 make_error(msg.to_string()) 365 // Parse our own error message that looks like "{} at line {} column {}" to work 366 // around erased-serde round-tripping the error through de::Error::custom. 367 fn make_error(mut msg: String) -> Error { 368 let (line, column) = parse_line_col(&mut msg).unwrap_or((0, 0)); 370 err: Box::new(ErrorImpl { 371 code: ErrorCode::Message(msg.into_boxed_str()), 378 fn parse_line_col(msg: &mut String) -> Option<(usize, usize)> { 379 let start_of_suffix = match msg.rfind(" at line ") { 380 Some(index) => index, 384 // Find start and end of line number. 385 let start_of_line = start_of_suffix + " at line ".len(); 386 let mut end_of_line = start_of_line; 387 while starts_with_digit(&msg[end_of_line..]) { 391 if !msg[end_of_line..].starts_with(" column ") { 395 // Find start and end of column number. 396 let start_of_column = end_of_line + " column ".len(); 397 let mut end_of_column = start_of_column; 398 while starts_with_digit(&msg[end_of_column..]) { 402 if end_of_column < msg.len() { 407 let line = match usize::from_str(&msg[start_of_line..end_of_line]) { 409 Err(_) => return None, 411 let column = match usize::from_str(&msg[start_of_column..end_of_column]) { 412 Ok(column) => column, 413 Err(_) => return None, 416 msg.truncate(start_of_suffix); 420 fn starts_with_digit(slice: &str) -> bool { 421 match slice.as_bytes().first() { 423 Some(&byte) => byte.is_ascii_digit(),