From 833c9cbf81dd03289ba0220476a2f03e2e612ac9 Mon Sep 17 00:00:00 2001 From: d34db4b3 Date: Thu, 3 Mar 2022 23:32:12 +0100 Subject: [PATCH 1/3] Adding serde_json::Error::code() functionnality --- src/error.rs | 225 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 +- tests/test.rs | 96 +++++++++++++++++++++ 3 files changed, 315 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index 6390c4368..014b4997d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -42,6 +42,14 @@ impl Error { self.err.column } + /// Specifies the cause of this error. + /// + /// Usefull when precise error handling is required or translation of + /// error messages is required. + pub fn code(&self) -> &ErrorCode { + &self.err.code + } + /// Categorizes the cause of this error. /// /// - `Category::Io` - failure to read or write bytes on an IO stream @@ -50,7 +58,14 @@ impl Error { /// - `Category::Eof` - unexpected end of the input data pub fn classify(&self) -> Category { match self.err.code { - ErrorCode::Message(_) => Category::Data, + ErrorCode::Message(_) + | ErrorCode::InvalidType(_, _) + | ErrorCode::InvalidValue(_, _) + | ErrorCode::InvalidLength(_, _) + | ErrorCode::UnknownVariant(_, _) + | ErrorCode::UnknownField(_, _) + | ErrorCode::MissingField(_) + | ErrorCode::DuplicateField(_) => Category::Data, ErrorCode::Io(_) => Category::Io, ErrorCode::EofWhileParsingList | ErrorCode::EofWhileParsingObject @@ -178,10 +193,35 @@ struct ErrorImpl { column: usize, } -pub(crate) enum ErrorCode { +/// This type describe all possible errors that can occur when serializing or +/// deserializing JSON data. +#[derive(Debug)] +pub enum ErrorCode { /// Catchall for syntax error messages Message(Box), + /// Different type than expected + InvalidType(Box, Box), + + /// Value of the right type but wrong for some other reason + InvalidValue(Box, Box), + + /// Sequence or map with too many or too few elements + InvalidLength(usize, Box), + + /// Enum type received a variant with an unrecognized name + UnknownVariant(Box, &'static [&'static str]), + + /// Struct type received a field with an unrecognized name. + UnknownField(Box, &'static [&'static str]), + + /// Struct type expected to receive a required field with + /// a particular name but it was not present + MissingField(Box), + + /// Struct type received more than one of the same field + DuplicateField(Box), + /// Some IO error occurred while serializing or deserializing. Io(io::Error), @@ -246,6 +286,23 @@ pub(crate) enum ErrorCode { RecursionLimitExceeded, } +impl PartialEq for ErrorCode { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Message(l0), Self::Message(r0)) => l0 == r0, + (Self::InvalidType(l0, l1), Self::InvalidType(r0, r1)) => l0 == r0 && l1 == r1, + (Self::InvalidValue(l0, l1), Self::InvalidValue(r0, r1)) => l0 == r0 && l1 == r1, + (Self::InvalidLength(l0, l1), Self::InvalidLength(r0, r1)) => l0 == r0 && l1 == r1, + (Self::UnknownVariant(l0, l1), Self::UnknownVariant(r0, r1)) => l0 == r0 && l1 == r1, + (Self::UnknownField(l0, l1), Self::UnknownField(r0, r1)) => l0 == r0 && l1 == r1, + (Self::MissingField(l0), Self::MissingField(r0)) => l0 == r0, + (Self::DuplicateField(l0), Self::DuplicateField(r0)) => l0 == r0, + (Self::Io(_), Self::Io(_)) => true, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + impl Error { #[cold] pub(crate) fn syntax(code: ErrorCode, line: usize, column: usize) -> Self { @@ -284,8 +341,51 @@ impl Error { impl Display for ErrorCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { + match self { ErrorCode::Message(ref msg) => f.write_str(msg), + ErrorCode::InvalidType(unexp, exp) => { + f.write_fmt(format_args!("invalid type: {}, expected {}", unexp, exp)) + } + ErrorCode::InvalidValue(unexp, exp) => { + f.write_fmt(format_args!("invalid value: {}, expected {}", unexp, exp)) + } + ErrorCode::InvalidLength(len, exp) => { + f.write_fmt(format_args!("invalid length {}, expected {}", len, exp)) + } + ErrorCode::UnknownVariant(variant, expected) => { + if expected.is_empty() { + f.write_fmt(format_args!( + "unknown variant `{}`, there are no variants", + variant + )) + } else { + f.write_fmt(format_args!( + "unknown variant `{}`, expected {}", + variant, + OneOf { names: expected } + )) + } + } + ErrorCode::UnknownField(field, expected) => { + if expected.is_empty() { + f.write_fmt(format_args!( + "unknown field `{}`, there are no fields", + field + )) + } else { + f.write_fmt(format_args!( + "unknown field `{}`, expected {}", + field, + OneOf { names: expected } + )) + } + } + ErrorCode::MissingField(ref field) => { + f.write_fmt(format_args!("missing field `{}`", field)) + } + ErrorCode::DuplicateField(ref field) => { + f.write_fmt(format_args!("duplicate field `{}`", field)) + } ErrorCode::Io(ref err) => Display::fmt(err, f), ErrorCode::EofWhileParsingList => f.write_str("EOF while parsing a list"), ErrorCode::EofWhileParsingObject => f.write_str("EOF while parsing an object"), @@ -367,10 +467,121 @@ impl de::Error for Error { #[cold] fn invalid_type(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self { - if let de::Unexpected::Unit = unexp { - Error::custom(format_args!("invalid type: null, expected {}", exp)) - } else { - Error::custom(format_args!("invalid type: {}, expected {}", unexp, exp)) + Error { + err: Box::new(ErrorImpl { + code: ErrorCode::InvalidType( + if unexp == de::Unexpected::Unit { + "null".into() + } else { + unexp.to_string().into_boxed_str() + }, + exp.to_string().into_boxed_str(), + ), + line: 0, + column: 0, + }), + } + } + + #[cold] + fn invalid_value(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self { + Error { + err: Box::new(ErrorImpl { + code: ErrorCode::InvalidValue( + unexp.to_string().into_boxed_str(), + exp.to_string().into_boxed_str(), + ), + line: 0, + column: 0, + }), + } + } + + #[cold] + fn invalid_length(len: usize, exp: &dyn de::Expected) -> Self { + Error { + err: Box::new(ErrorImpl { + code: ErrorCode::InvalidLength(len, exp.to_string().into_boxed_str()), + line: 0, + column: 0, + }), + } + } + + #[cold] + fn unknown_variant(variant: &str, expected: &'static [&'static str]) -> Self { + Error { + err: Box::new(ErrorImpl { + code: ErrorCode::UnknownVariant(variant.to_string().into_boxed_str(), expected), + line: 0, + column: 0, + }), + } + } + + #[cold] + fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self { + Error { + err: Box::new(ErrorImpl { + code: ErrorCode::UnknownField(field.to_string().into_boxed_str(), expected), + line: 0, + column: 0, + }), + } + } + + #[cold] + fn missing_field(field: &'static str) -> Self { + Error { + err: Box::new(ErrorImpl { + code: ErrorCode::MissingField(field.to_string().into_boxed_str()), + line: 0, + column: 0, + }), + } + } + + #[cold] + fn duplicate_field(field: &'static str) -> Self { + Error { + err: Box::new(ErrorImpl { + code: ErrorCode::DuplicateField(field.to_string().into_boxed_str()), + line: 0, + column: 0, + }), + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/// Used in error messages. +/// +/// - expected `a` +/// - expected `a` or `b` +/// - expected one of `a`, `b`, `c` +/// +/// The slice of names must not be empty. +struct OneOf { + names: &'static [&'static str], +} + +impl Display for OneOf { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self.names.len() { + 0 => panic!(), // special case elsewhere + 1 => write!(formatter, "`{}`", self.names[0]), + 2 => write!(formatter, "`{}` or `{}`", self.names[0], self.names[1]), + _ => { + write!(formatter, "one of ")?; + for (i, alt) in self.names.iter().enumerate() { + if i > 0 { + write!(formatter, ", ")?; + } + write!(formatter, "`{}`", alt)?; + } + Ok(()) + } } } } diff --git a/src/lib.rs b/src/lib.rs index 611c9144a..16b48ece6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -373,7 +373,7 @@ pub use crate::de::from_reader; #[doc(inline)] pub use crate::de::{from_slice, from_str, Deserializer, StreamDeserializer}; #[doc(inline)] -pub use crate::error::{Error, Result}; +pub use crate::error::{Error, ErrorCode, Result}; #[doc(inline)] pub use crate::ser::{to_string, to_string_pretty, to_vec, to_vec_pretty}; #[cfg(feature = "std")] diff --git a/tests/test.rs b/tests/test.rs index b11635e75..7670eb635 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -2384,3 +2384,99 @@ fn hash_positive_and_negative_zero() { assert_eq!(hash(k1), hash(k2)); } } + +#[test] +fn test_error_codes() { + use serde::Deserializer; + use serde_json::ErrorCode; + + fn contains_test<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let v = String::deserialize(deserializer)?; + if v.contains("test") { + Ok(v) + } else { + Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(&v), + &"a string containing the substring `test`", + )) + } + } + + #[derive(Deserialize, Debug)] + #[serde(deny_unknown_fields)] + struct Test { + #[serde(deserialize_with = "contains_test")] + #[allow(dead_code)] + required: String, + #[allow(dead_code)] + seq: Option<[u8; 2]>, + #[allow(dead_code)] + en: Option, + } + + #[derive(Deserialize, Debug)] + enum Enum { + Variant1, + Variant2, + } + + assert_eq!( + serde_json::from_str::(r#"{"required":1}"#) + .unwrap_err() + .code(), + &ErrorCode::InvalidType( + "integer `1`".to_string().into_boxed_str(), + "a string".to_string().into_boxed_str() + ) + ); + assert_eq!( + serde_json::from_str::(r#"{"required":"ok"}"#) + .unwrap_err() + .code(), + &ErrorCode::InvalidValue( + "string \"ok\"".to_string().into_boxed_str(), + "a string containing the substring `test`" + .to_string() + .into_boxed_str() + ) + ); + // SHOULD PROBABLY BE + // &ErrorCode::InvalidLength(3, "an array of length 2".to_string().into_boxed_str()) + assert_eq!( + serde_json::from_str::(r#"{"required":"test","seq":[0,1,2]}"#) + .unwrap_err() + .code(), + &ErrorCode::TrailingCharacters + ); + assert_eq!( + serde_json::from_str::(r#"{"required":"test","seq":[0]}"#) + .unwrap_err() + .code(), + &ErrorCode::InvalidLength(1, "an array of length 2".to_string().into_boxed_str()) + ); + assert_eq!( + serde_json::from_str::(r#"{"en":"Variant3"}"#) + .unwrap_err() + .code(), + &ErrorCode::UnknownVariant( + "Variant3".to_string().into_boxed_str(), + &["Variant1", "Variant2"] + ) + ); + assert_eq!( + serde_json::from_str::(r#"{"required":"test","unknown":1}"#) + .unwrap_err() + .code(), + &ErrorCode::UnknownField( + "unknown".to_string().into_boxed_str(), + &["required", "seq", "en"] + ) + ); + assert_eq!( + serde_json::from_str::(r#"{}"#).unwrap_err().code(), + &ErrorCode::MissingField("required".to_string().into_boxed_str()) + ); +} From fd29bd317c2a9e9cfa68881448c6dfa498f1abb2 Mon Sep 17 00:00:00 2001 From: tpot Date: Fri, 4 Mar 2022 22:37:08 +0100 Subject: [PATCH 2/3] debug fix for io::Error --- src/error.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 014b4997d..b3f3700c0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -195,7 +195,6 @@ struct ErrorImpl { /// This type describe all possible errors that can occur when serializing or /// deserializing JSON data. -#[derive(Debug)] pub enum ErrorCode { /// Catchall for syntax error messages Message(Box), @@ -286,6 +285,15 @@ pub enum ErrorCode { RecursionLimitExceeded, } +impl Debug for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(_) => f.debug_tuple("Io").finish(), + error_code => Debug::fmt(&error_code, f), + } + } +} + impl PartialEq for ErrorCode { fn eq(&self, other: &Self) -> bool { match (self, other) { From 01b68007f3e878b2cb284738442ee6ff6e51a196 Mon Sep 17 00:00:00 2001 From: tpot Date: Fri, 4 Mar 2022 23:20:34 +0100 Subject: [PATCH 3/3] enum variants on type aliases --- src/error.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index b3f3700c0..8d386940b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -288,7 +288,7 @@ pub enum ErrorCode { impl Debug for ErrorCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Io(_) => f.debug_tuple("Io").finish(), + ErrorCode::Io(_) => f.debug_tuple("Io").finish(), error_code => Debug::fmt(&error_code, f), } } @@ -297,15 +297,25 @@ impl Debug for ErrorCode { impl PartialEq for ErrorCode { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Message(l0), Self::Message(r0)) => l0 == r0, - (Self::InvalidType(l0, l1), Self::InvalidType(r0, r1)) => l0 == r0 && l1 == r1, - (Self::InvalidValue(l0, l1), Self::InvalidValue(r0, r1)) => l0 == r0 && l1 == r1, - (Self::InvalidLength(l0, l1), Self::InvalidLength(r0, r1)) => l0 == r0 && l1 == r1, - (Self::UnknownVariant(l0, l1), Self::UnknownVariant(r0, r1)) => l0 == r0 && l1 == r1, - (Self::UnknownField(l0, l1), Self::UnknownField(r0, r1)) => l0 == r0 && l1 == r1, - (Self::MissingField(l0), Self::MissingField(r0)) => l0 == r0, - (Self::DuplicateField(l0), Self::DuplicateField(r0)) => l0 == r0, - (Self::Io(_), Self::Io(_)) => true, + (ErrorCode::Message(l0), ErrorCode::Message(r0)) => l0 == r0, + (ErrorCode::InvalidType(l0, l1), ErrorCode::InvalidType(r0, r1)) => { + l0 == r0 && l1 == r1 + } + (ErrorCode::InvalidValue(l0, l1), ErrorCode::InvalidValue(r0, r1)) => { + l0 == r0 && l1 == r1 + } + (ErrorCode::InvalidLength(l0, l1), ErrorCode::InvalidLength(r0, r1)) => { + l0 == r0 && l1 == r1 + } + (ErrorCode::UnknownVariant(l0, l1), ErrorCode::UnknownVariant(r0, r1)) => { + l0 == r0 && l1 == r1 + } + (ErrorCode::UnknownField(l0, l1), ErrorCode::UnknownField(r0, r1)) => { + l0 == r0 && l1 == r1 + } + (ErrorCode::MissingField(l0), ErrorCode::MissingField(r0)) => l0 == r0, + (ErrorCode::DuplicateField(l0), ErrorCode::DuplicateField(r0)) => l0 == r0, + (ErrorCode::Io(_), ErrorCode::Io(_)) => true, _ => core::mem::discriminant(self) == core::mem::discriminant(other), } }