blob: 09ec2f97f47c81f7de19543645e3845d323750f5 [file] [log] [blame]
use crate::Span;
use std::fmt::{self, Debug, Display};
/// Error that can occur when deserializing TOML.
#[derive(Debug, Clone)]
pub struct Error {
/// The error kind
pub kind: ErrorKind,
/// The span where the error occurs.
///
/// Note some [`ErrorKind`] contain additional span information
pub span: Span,
/// Line and column information, only available for errors coming from the parser
pub line_info: Option<(usize, usize)>,
}
impl std::error::Error for Error {}
impl From<(ErrorKind, Span)> for Error {
fn from((kind, span): (ErrorKind, Span)) -> Self {
Self {
kind,
span,
line_info: None,
}
}
}
/// Errors that can occur when deserializing a type.
#[derive(Debug, Clone)]
pub enum ErrorKind {
/// EOF was reached when looking for a value.
UnexpectedEof,
/// An invalid character not allowed in a string was found.
InvalidCharInString(char),
/// An invalid character was found as an escape.
InvalidEscape(char),
/// An invalid character was found in a hex escape.
InvalidHexEscape(char),
/// An invalid escape value was specified in a hex escape in a string.
///
/// Valid values are in the plane of unicode codepoints.
InvalidEscapeValue(u32),
/// An unexpected character was encountered, typically when looking for a
/// value.
Unexpected(char),
/// An unterminated string was found where EOF was found before the ending
/// EOF mark.
UnterminatedString,
/// A number failed to parse.
InvalidNumber,
/// The number in the toml file cannot be losslessly converted to the specified
/// number type
OutOfRange(&'static str),
/// Wanted one sort of token, but found another.
Wanted {
/// Expected token type.
expected: &'static str,
/// Actually found token type.
found: &'static str,
},
/// A duplicate table definition was found.
DuplicateTable {
/// The name of the duplicate table
name: String,
/// The span where the table was first defined
first: Span,
},
/// Duplicate key in table.
DuplicateKey {
/// The duplicate key
key: String,
/// The span where the first key is located
first: Span,
},
/// A previously defined table was redefined as an array.
RedefineAsArray,
/// Multiline strings are not allowed for key.
MultilineStringKey,
/// A custom error which could be generated when deserializing a particular
/// type.
Custom(std::borrow::Cow<'static, str>),
/// Dotted key attempted to extend something that is not a table.
DottedKeyInvalidType {
/// The span where the non-table value was defined
first: Span,
},
/// An unexpected key was encountered.
///
/// Used when deserializing a struct with a limited set of fields.
UnexpectedKeys {
/// The unexpected keys.
keys: Vec<(String, Span)>,
/// The list of keys that were expected for the table
expected: Vec<String>,
},
/// Unquoted string was found when quoted one was expected.
UnquotedString,
/// A required field is missing from a table
MissingField(&'static str),
/// A field in the table is deprecated and the new key should be used instead
Deprecated {
/// The deprecated key name
old: &'static str,
/// The key name that should be used instead
new: &'static str,
},
/// An unexpected value was encountered
UnexpectedValue {
/// The list of values that could have been used, eg. typically enum variants
expected: &'static [&'static str],
},
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedEof => f.write_str("unexpected-eof"),
Self::Custom(..) => f.write_str("custom"),
Self::DottedKeyInvalidType { .. } => f.write_str("dotted-key-invalid-type"),
Self::DuplicateKey { .. } => f.write_str("duplicate-key"),
Self::DuplicateTable { .. } => f.write_str("duplicate-table"),
Self::UnexpectedKeys { .. } => f.write_str("unexpected-keys"),
Self::UnquotedString => f.write_str("unquoted-string"),
Self::MultilineStringKey => f.write_str("multiline-string-key"),
Self::RedefineAsArray => f.write_str("redefine-as-array"),
Self::InvalidCharInString(..) => f.write_str("invalid-char-in-string"),
Self::InvalidEscape(..) => f.write_str("invalid-escape"),
Self::InvalidEscapeValue(..) => f.write_str("invalid-escape-value"),
Self::InvalidHexEscape(..) => f.write_str("invalid-hex-escape"),
Self::Unexpected(..) => f.write_str("unexpected"),
Self::UnterminatedString => f.write_str("unterminated-string"),
Self::InvalidNumber => f.write_str("invalid-number"),
Self::OutOfRange(_) => f.write_str("out-of-range"),
Self::Wanted { .. } => f.write_str("wanted"),
Self::MissingField(..) => f.write_str("missing-field"),
Self::Deprecated { .. } => f.write_str("deprecated"),
Self::UnexpectedValue { .. } => f.write_str("unexpected-value"),
}
}
}
struct Escape(char);
impl fmt::Display for Escape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::Write as _;
if self.0.is_whitespace() {
for esc in self.0.escape_default() {
f.write_char(esc)?;
}
Ok(())
} else {
f.write_char(self.0)
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ErrorKind::UnexpectedEof => f.write_str("unexpected eof encountered")?,
ErrorKind::InvalidCharInString(c) => {
write!(f, "invalid character in string: `{}`", Escape(*c))?;
}
ErrorKind::InvalidEscape(c) => {
write!(f, "invalid escape character in string: `{}`", Escape(*c))?;
}
ErrorKind::InvalidHexEscape(c) => write!(
f,
"invalid hex escape character in string: `{}`",
Escape(*c)
)?,
ErrorKind::InvalidEscapeValue(c) => write!(f, "invalid escape value: `{c}`")?,
ErrorKind::Unexpected(c) => write!(f, "unexpected character found: `{}`", Escape(*c))?,
ErrorKind::UnterminatedString => f.write_str("unterminated string")?,
ErrorKind::Wanted { expected, found } => {
write!(f, "expected {expected}, found {found}")?;
}
ErrorKind::InvalidNumber => f.write_str("invalid number")?,
ErrorKind::OutOfRange(kind) => write!(f, "out of range of '{kind}'")?,
ErrorKind::DuplicateTable { name, .. } => {
write!(f, "redefinition of table `{name}`")?;
}
ErrorKind::DuplicateKey { key, .. } => {
write!(f, "duplicate key: `{key}`")?;
}
ErrorKind::RedefineAsArray => f.write_str("table redefined as array")?,
ErrorKind::MultilineStringKey => {
f.write_str("multiline strings are not allowed for key")?;
}
ErrorKind::Custom(message) => f.write_str(message)?,
ErrorKind::DottedKeyInvalidType { .. } => {
f.write_str("dotted key attempted to extend non-table type")?;
}
ErrorKind::UnexpectedKeys { keys, expected } => write!(
f,
"unexpected keys in table: `{keys:?}`\nexpected: {expected:?}"
)?,
ErrorKind::UnquotedString => {
f.write_str("invalid TOML value, did you mean to use a quoted string?")?;
}
ErrorKind::MissingField(field) => write!(f, "missing field '{field}' in table")?,
ErrorKind::Deprecated { old, new } => {
write!(f, "field '{old}' is deprecated, '{new}' has replaced it")?;
}
ErrorKind::UnexpectedValue { expected } => write!(f, "expected '{expected:?}'")?,
}
// if !self.key.is_empty() {
// write!(f, " for key `")?;
// for (i, k) in self.key.iter().enumerate() {
// if i > 0 {
// write!(f, ".")?;
// }
// write!(f, "{}", k)?;
// }
// write!(f, "`")?;
// }
// if let Some(line) = self.line {
// write!(f, " at line {} column {}", line + 1, self.col + 1)?;
// }
Ok(())
}
}
#[cfg(feature = "reporting")]
#[cfg_attr(docsrs, doc(cfg(feature = "reporting")))]
impl Error {
/// Converts this [`Error`] into a [`codespan_reporting::diagnostic::Diagnostic`]
pub fn to_diagnostic<FileId: Copy + PartialEq>(
&self,
fid: FileId,
) -> codespan_reporting::diagnostic::Diagnostic<FileId> {
let diag =
codespan_reporting::diagnostic::Diagnostic::error().with_code(self.kind.to_string());
use codespan_reporting::diagnostic::Label;
let diag = match &self.kind {
ErrorKind::DuplicateKey { first, .. } => diag.with_labels(vec![
Label::secondary(fid, *first).with_message("first key instance"),
Label::primary(fid, self.span).with_message("duplicate key"),
]),
ErrorKind::Unexpected(c) => diag.with_labels(vec![Label::primary(fid, self.span)
.with_message(format!("unexpected character '{}'", Escape(*c)))]),
ErrorKind::InvalidCharInString(c) => {
diag.with_labels(vec![Label::primary(fid, self.span)
.with_message(format!("invalid character '{}' in string", Escape(*c)))])
}
ErrorKind::InvalidEscape(c) => diag.with_labels(vec![Label::primary(fid, self.span)
.with_message(format!(
"invalid escape character '{}' in string",
Escape(*c)
))]),
ErrorKind::InvalidEscapeValue(_) => diag
.with_labels(vec![
Label::primary(fid, self.span).with_message("invalid escape value")
]),
ErrorKind::InvalidNumber => diag.with_labels(vec![
Label::primary(fid, self.span).with_message("unable to parse number")
]),
ErrorKind::OutOfRange(kind) => diag
.with_message(format!("number is out of range of '{kind}'"))
.with_labels(vec![Label::primary(fid, self.span)]),
ErrorKind::Wanted { expected, .. } => diag
.with_labels(vec![
Label::primary(fid, self.span).with_message(format!("expected {expected}"))
]),
ErrorKind::MultilineStringKey => diag.with_labels(vec![
Label::primary(fid, self.span).with_message("multiline keys are not allowed")
]),
ErrorKind::UnterminatedString => diag
.with_labels(vec![Label::primary(fid, self.span)
.with_message("eof reached before string terminator")]),
ErrorKind::DuplicateTable { first, .. } => diag.with_labels(vec![
Label::secondary(fid, *first).with_message("first table instance"),
Label::primary(fid, self.span).with_message("duplicate table"),
]),
ErrorKind::InvalidHexEscape(c) => diag
.with_labels(vec![Label::primary(fid, self.span)
.with_message(format!("invalid hex escape '{}'", Escape(*c)))]),
ErrorKind::UnquotedString => diag.with_labels(vec![
Label::primary(fid, self.span).with_message("string is not quoted")
]),
ErrorKind::UnexpectedKeys { keys, expected } => diag
.with_message(format!(
"found {} unexpected keys, expected: {expected:?}",
keys.len()
))
.with_labels(
keys.iter()
.map(|(_name, span)| Label::secondary(fid, *span))
.collect(),
),
ErrorKind::MissingField(field) => diag
.with_message(format!("missing field '{field}'"))
.with_labels(vec![
Label::primary(fid, self.span).with_message("table with missing field")
]),
ErrorKind::Deprecated { new, .. } => diag
.with_message(format!(
"deprecated field enountered, '{new}' should be used instead"
))
.with_labels(vec![
Label::primary(fid, self.span).with_message("deprecated field")
]),
ErrorKind::UnexpectedValue { expected } => diag
.with_message(format!("expected '{expected:?}'"))
.with_labels(vec![
Label::primary(fid, self.span).with_message("unexpected value")
]),
ErrorKind::UnexpectedEof => diag
.with_message("unexpected end of file")
.with_labels(vec![Label::primary(fid, self.span)]),
ErrorKind::DottedKeyInvalidType { first } => {
diag.with_message(self.to_string()).with_labels(vec![
Label::primary(fid, self.span).with_message("attempted to extend table here"),
Label::secondary(fid, *first).with_message("non-table"),
])
}
ErrorKind::RedefineAsArray => diag
.with_message(self.to_string())
.with_labels(vec![Label::primary(fid, self.span)]),
ErrorKind::Custom(msg) => diag
.with_message(msg.to_string())
.with_labels(vec![Label::primary(fid, self.span)]),
};
diag
}
}
/// When deserializing, it's possible to collect multiple errors instead of earlying
/// out at the first error
#[derive(Debug)]
pub struct DeserError {
/// The set of errors that occurred during deserialization
pub errors: Vec<Error>,
}
impl DeserError {
/// Merges errors from another [`Self`]
#[inline]
pub fn merge(&mut self, mut other: Self) {
self.errors.append(&mut other.errors);
}
}
impl std::error::Error for DeserError {}
impl From<Error> for DeserError {
fn from(value: Error) -> Self {
Self {
errors: vec![value],
}
}
}
impl fmt::Display for DeserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for err in &self.errors {
writeln!(f, "{err}")?;
}
Ok(())
}
}