use crate::libyaml::{emitter, error as libyaml}; use crate::path::Path; use serde::{de, ser}; use std::error::Error as StdError; use std::fmt::{self, Debug, Display}; use std::io; use std::result; use std::string; use std::sync::Arc; /// An error that happened serializing or deserializing YAML data. pub struct Error(Box); /// Alias for a `Result` with the error type `serde_yaml::Error`. pub type Result = result::Result; #[derive(Debug)] pub(crate) enum ErrorImpl { Message(String, Option), Libyaml(libyaml::Error), Io(io::Error), FromUtf8(string::FromUtf8Error), EndOfStream, MoreThanOneDocument, RecursionLimitExceeded(libyaml::Mark), RepetitionLimitExceeded, BytesUnsupported, UnknownAnchor(libyaml::Mark), SerializeNestedEnum, ScalarInMerge, TaggedInMerge, ScalarInMergeElement, SequenceInMergeElement, EmptyTag, FailedToParseNumber, Shared(Arc), } #[derive(Debug)] pub(crate) struct Pos { mark: libyaml::Mark, path: String, } /// The input location that an error occured. #[derive(Debug)] pub struct Location { index: usize, line: usize, column: usize, } impl Location { /// The byte index of the error pub fn index(&self) -> usize { self.index } /// The line of the error pub fn line(&self) -> usize { self.line } /// The column of the error pub fn column(&self) -> usize { self.column } // This is to keep decoupled with the yaml crate #[doc(hidden)] fn from_mark(mark: libyaml::Mark) -> Self { Location { index: mark.index() as usize, // `line` and `column` returned from libyaml are 0-indexed but all error messages add +1 to this value line: mark.line() as usize + 1, column: mark.column() as usize + 1, } } } impl Error { /// Returns the Location from the error if one exists. /// /// Not all types of errors have a location so this can return `None`. /// /// # Examples /// /// ``` /// # use serde_yaml::{Value, Error}; /// # /// // The `@` character as the first character makes this invalid yaml /// let invalid_yaml: Result = serde_yaml::from_str("@invalid_yaml"); /// /// let location = invalid_yaml.unwrap_err().location().unwrap(); /// /// assert_eq!(location.line(), 1); /// assert_eq!(location.column(), 1); /// ``` pub fn location(&self) -> Option { self.0.location() } } pub(crate) fn new(inner: ErrorImpl) -> Error { Error(Box::new(inner)) } pub(crate) fn shared(shared: Arc) -> Error { Error(Box::new(ErrorImpl::Shared(shared))) } pub(crate) fn fix_mark(mut error: Error, mark: libyaml::Mark, path: Path) -> Error { if let ErrorImpl::Message(_, none @ None) = error.0.as_mut() { *none = Some(Pos { mark, path: path.to_string(), }); } error } impl Error { pub(crate) fn shared(self) -> Arc { if let ErrorImpl::Shared(err) = *self.0 { err } else { Arc::from(self.0) } } } impl From for Error { fn from(err: libyaml::Error) -> Self { Error(Box::new(ErrorImpl::Libyaml(err))) } } impl From for Error { fn from(err: emitter::Error) -> Self { match err { emitter::Error::Libyaml(err) => Self::from(err), emitter::Error::Io(err) => new(ErrorImpl::Io(err)), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { self.0.source() } } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.display(f) } } // Remove two layers of verbosity from the debug representation. Humans often // end up seeing this representation because it is what unwrap() shows. impl Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.debug(f) } } impl ser::Error for Error { fn custom(msg: T) -> Self { Error(Box::new(ErrorImpl::Message(msg.to_string(), None))) } } impl de::Error for Error { fn custom(msg: T) -> Self { Error(Box::new(ErrorImpl::Message(msg.to_string(), None))) } } impl ErrorImpl { fn location(&self) -> Option { self.mark().map(Location::from_mark) } fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { ErrorImpl::Io(err) => err.source(), ErrorImpl::FromUtf8(err) => err.source(), ErrorImpl::Shared(err) => err.source(), _ => None, } } fn mark(&self) -> Option { match self { ErrorImpl::Message(_, Some(Pos { mark, path: _ })) | ErrorImpl::RecursionLimitExceeded(mark) | ErrorImpl::UnknownAnchor(mark) => Some(*mark), ErrorImpl::Libyaml(err) => Some(err.mark()), ErrorImpl::Shared(err) => err.mark(), _ => None, } } fn message_no_mark(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ErrorImpl::Message(msg, None) => f.write_str(msg), ErrorImpl::Message(msg, Some(Pos { mark: _, path })) => { if path != "." { write!(f, "{}: ", path)?; } f.write_str(msg) } ErrorImpl::Libyaml(_) => unreachable!(), ErrorImpl::Io(err) => Display::fmt(err, f), ErrorImpl::FromUtf8(err) => Display::fmt(err, f), ErrorImpl::EndOfStream => f.write_str("EOF while parsing a value"), ErrorImpl::MoreThanOneDocument => f.write_str( "deserializing from YAML containing more than one document is not supported", ), ErrorImpl::RecursionLimitExceeded(_mark) => f.write_str("recursion limit exceeded"), ErrorImpl::RepetitionLimitExceeded => f.write_str("repetition limit exceeded"), ErrorImpl::BytesUnsupported => { f.write_str("serialization and deserialization of bytes in YAML is not implemented") } ErrorImpl::UnknownAnchor(_mark) => f.write_str("unknown anchor"), ErrorImpl::SerializeNestedEnum => { f.write_str("serializing nested enums in YAML is not supported yet") } ErrorImpl::ScalarInMerge => { f.write_str("expected a mapping or list of mappings for merging, but found scalar") } ErrorImpl::TaggedInMerge => f.write_str("unexpected tagged value in merge"), ErrorImpl::ScalarInMergeElement => { f.write_str("expected a mapping for merging, but found scalar") } ErrorImpl::SequenceInMergeElement => { f.write_str("expected a mapping for merging, but found sequence") } ErrorImpl::EmptyTag => f.write_str("empty YAML tag is not allowed"), ErrorImpl::FailedToParseNumber => f.write_str("failed to parse YAML number"), ErrorImpl::Shared(_) => unreachable!(), } } fn display(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ErrorImpl::Libyaml(err) => Display::fmt(err, f), ErrorImpl::Shared(err) => err.display(f), _ => { self.message_no_mark(f)?; if let Some(mark) = self.mark() { if mark.line() != 0 || mark.column() != 0 { write!(f, " at {}", mark)?; } } Ok(()) } } } fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ErrorImpl::Libyaml(err) => Debug::fmt(err, f), ErrorImpl::Shared(err) => err.debug(f), _ => { f.write_str("Error(")?; struct MessageNoMark<'a>(&'a ErrorImpl); impl<'a> Display for MessageNoMark<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.message_no_mark(f) } } let msg = MessageNoMark(self).to_string(); Debug::fmt(&msg, f)?; if let Some(mark) = self.mark() { write!( f, ", line: {}, column: {}", mark.line() + 1, mark.column() + 1, )?; } f.write_str(")") } } } }