Skip to content

Commit

Permalink
Annotate serde struct+enum errors with struct/enum/variant names
Browse files Browse the repository at this point in the history
  • Loading branch information
juntyr committed Aug 15, 2022
1 parent 6f84bf0 commit 9407e2e
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 20 deletions.
73 changes: 67 additions & 6 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod value;
pub struct Deserializer<'de> {
bytes: Bytes<'de>,
newtype_variant: bool,
last_identifier: Option<&'de str>,
}

impl<'de> Deserializer<'de> {
Expand All @@ -46,6 +47,7 @@ impl<'de> Deserializer<'de> {
let mut deserializer = Deserializer {
bytes: Bytes::new(input)?,
newtype_variant: false,
last_identifier: None,
};

deserializer.bytes.exts |= options.default_extensions;
Expand Down Expand Up @@ -528,7 +530,19 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
let old_newtype_variant = self.newtype_variant;
self.newtype_variant = false;

let value = visitor.visit_map(CommaSeparated::new(b')', self))?;
let value = visitor
.visit_map(CommaSeparated::new(b')', self))
.map_err(|err| {
struct_error_name(
err,
if !old_newtype_variant && !name.is_empty() {
Some(name)
} else {
None
},
)
})?;

self.bytes.comma()?;

if old_newtype_variant || self.bytes.consume(")") {
Expand All @@ -545,7 +559,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {

fn deserialize_enum<V>(
self,
_name: &'static str,
name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
Expand All @@ -554,14 +568,30 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
{
self.newtype_variant = false;

visitor.visit_enum(Enum::new(self))
match visitor.visit_enum(Enum::new(self)) {
Ok(value) => Ok(value),
Err(Error::NoSuchEnumVariant {
expected,
found,
outer: None,
}) if !name.is_empty() => Err(Error::NoSuchEnumVariant {
expected,
found,
outer: Some(String::from(name)),
}),
Err(e) => Err(e),
}
}

fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_str(str::from_utf8(self.bytes.identifier()?).map_err(Error::from)?)
let identifier = str::from_utf8(self.bytes.identifier()?).map_err(Error::from)?;

self.last_identifier = Some(identifier);

visitor.visit_str(identifier)
}

fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
Expand Down Expand Up @@ -699,6 +729,8 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> {
where
T: DeserializeSeed<'de>,
{
let newtype_variant = self.de.last_identifier;

self.de.bytes.skip_ws()?;

if self.de.bytes.consume("(") {
Expand All @@ -710,7 +742,9 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> {
.exts
.contains(Extensions::UNWRAP_VARIANT_NEWTYPES);

let val = seed.deserialize(&mut *self.de)?;
let val = seed
.deserialize(&mut *self.de)
.map_err(|err| struct_error_name(err, newtype_variant))?;

self.de.newtype_variant = false;

Expand Down Expand Up @@ -739,8 +773,35 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> {
where
V: Visitor<'de>,
{
let struct_variant = self.de.last_identifier;

self.de.bytes.skip_ws()?;

self.de.deserialize_struct("", fields, visitor)
self.de
.deserialize_struct("", fields, visitor)
.map_err(|err| struct_error_name(err, struct_variant))
}
}

fn struct_error_name(error: Error, name: Option<&str>) -> Error {
match error {
Error::NoSuchStructField {
expected,
found,
outer: None,
} => Error::NoSuchStructField {
expected,
found,
outer: name.map(ToOwned::to_owned),
},
Error::MissingStructField { field, outer: None } => Error::MissingStructField {
field,
outer: name.map(ToOwned::to_owned),
},
Error::DuplicateStructField { field, outer: None } => Error::DuplicateStructField {
field,
outer: name.map(ToOwned::to_owned),
},
e => e,
}
}
62 changes: 50 additions & 12 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,21 @@ pub enum Error {
NoSuchEnumVariant {
expected: &'static [&'static str],
found: String,
outer: Option<String>,
},
NoSuchStructField {
expected: &'static [&'static str],
found: String,
outer: Option<String>,
},
MissingStructField {
field: &'static str,
outer: Option<String>,
},
DuplicateStructField {
field: &'static str,
outer: Option<String>,
},
MissingStructField(&'static str),
DuplicateStructField(&'static str),
}

impl fmt::Display for SpannedError {
Expand Down Expand Up @@ -173,11 +181,23 @@ impl fmt::Display for Error {
Error::NoSuchEnumVariant {
expected,
ref found,
ref outer,
} => {
f.write_str("Unexpected ")?;

if outer.is_none() {
f.write_str("enum ")?;
}

write!(f, "variant named `{}`", found)?;

if let Some(outer) = outer {
write!(f, "in enum `{}`", outer)?;
}

write!(
f,
"Unexpected enum variant named `{}`, {}",
found,
", {}",
OneOf {
alts: expected,
none: "variants"
Expand All @@ -187,22 +207,38 @@ impl fmt::Display for Error {
Error::NoSuchStructField {
expected,
ref found,
ref outer,
} => {
write!(f, "Unexpected field named `{}`", found)?;

if let Some(outer) = outer {
write!(f, "in `{}`", outer)?;
}

write!(
f,
"Unexpected field named `{}`, {}",
found,
", {}",
OneOf {
alts: expected,
none: "fields"
}
)
}
Error::MissingStructField(field) => {
write!(f, "Unexpected missing field `{}`", field)
Error::MissingStructField { field, ref outer } => {
write!(f, "Unexpected missing field `{}`", field)?;

match outer {
Some(outer) => write!(f, " in `{}`", outer),
None => Ok(()),
}
}
Error::DuplicateStructField(field) => {
write!(f, "Unexpected duplicate field `{}`", field)
Error::DuplicateStructField { field, ref outer } => {
write!(f, "Unexpected duplicate field `{}`", field)?;

match outer {
Some(outer) => write!(f, " in `{}`", outer),
None => Ok(()),
}
}
}
}
Expand Down Expand Up @@ -262,6 +298,7 @@ impl de::Error for Error {
Error::NoSuchEnumVariant {
expected,
found: variant.to_string(),
outer: None,
}
}

Expand All @@ -270,17 +307,18 @@ impl de::Error for Error {
Error::NoSuchStructField {
expected,
found: field.to_string(),
outer: None,
}
}

#[cold]
fn missing_field(field: &'static str) -> Self {
Error::MissingStructField(field)
Error::MissingStructField { field, outer: None }
}

#[cold]
fn duplicate_field(field: &'static str) -> Self {
Error::DuplicateStructField(field)
Error::DuplicateStructField { field, outer: None }
}
}

Expand Down
12 changes: 10 additions & 2 deletions tests/203_error_positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fn test_error_positions() {
code: Error::NoSuchEnumVariant {
expected: &["TupleVariant", "StructVariant"],
found: String::from("NotAVariant"),
outer: Some(String::from("Test")),
},
position: Position { line: 1, col: 12 },
})
Expand All @@ -74,6 +75,7 @@ fn test_error_positions() {
code: Error::NoSuchStructField {
expected: &["a", "b", "c"],
found: String::from("d"),
outer: Some(String::from("StructVariant")),
},
position: Position { line: 1, col: 39 },
})
Expand All @@ -82,15 +84,21 @@ fn test_error_positions() {
assert_eq!(
ron::from_str::<Test>("StructVariant(a: true, c: -42)"),
Err(SpannedError {
code: Error::MissingStructField("b"),
code: Error::MissingStructField {
field: "b",
outer: Some(String::from("StructVariant")),
},
position: Position { line: 1, col: 30 },
})
);

assert_eq!(
ron::from_str::<Test>("StructVariant(a: true, b: 1, a: false, c: -42)"),
Err(SpannedError {
code: Error::DuplicateStructField("a"),
code: Error::DuplicateStructField {
field: "a",
outer: Some(String::from("StructVariant")),
},
position: Position { line: 1, col: 31 },
})
);
Expand Down
Loading

0 comments on commit 9407e2e

Please sign in to comment.