Skip to content

Commit

Permalink
serde: record errors as a struct with fields for sources (#91)
Browse files Browse the repository at this point in the history
Depends on #89

This PR changes `valuable-serde`'s recording of `dyn Error` values to
record the error as a `serde` struct with `message` and `source` fields.
This way, we can serialize errors with source chains more nicely.

When the backtrace support for `std::error::Error` is stable, we could
also record backtraces as a field. We could even consider adding a build
script to detect the nightly compiler and conditionally enable a `cfg`
for backtrace support, but that seems better left to a follow-up.
  • Loading branch information
hawkw committed Mar 8, 2022
1 parent 461c3d7 commit 5624fe1
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 1 deletion.
18 changes: 17 additions & 1 deletion valuable-serde/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ where
#[cfg(feature = "std")]
Value::Path(p) => Serialize::serialize(p, serializer),
#[cfg(feature = "std")]
Value::Error(e) => serializer.collect_str(e),
Value::Error(e) => SerializeError(e).serialize(serializer),

v => unimplemented!("{:?}", v),
}
Expand Down Expand Up @@ -668,3 +668,19 @@ impl<S: Serializer> Visit for VisitDynamic<'_, S> {
}
}
}

struct SerializeError<'a>(&'a dyn std::error::Error);
impl Serialize for SerializeError<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
struct CollectStr<'a>(&'a dyn std::error::Error);
impl Serialize for CollectStr<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(&self.0)
}
}
let mut s = serializer.serialize_struct("Error", 2)?;
s.serialize_field("message", &CollectStr(self.0))?;
s.serialize_field("source", &self.0.source().map(SerializeError))?;
s.end()
}
}
72 changes: 72 additions & 0 deletions valuable-serde/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,75 @@ fn test_dyn_enum() {
],
);
}

#[test]
fn test_errors() {
use std::{error::Error, fmt};

#[derive(Debug)]
struct TestError {
message: &'static str,
source: Option<Box<dyn Error + 'static>>,
}

impl fmt::Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.message)
}
}

impl Error for TestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source.as_deref()
}
}

let no_source = TestError {
message: "an error occurred",
source: None,
};

assert_ser_eq!(
&Serializable::new(&no_source as &(dyn Error + 'static)),
&[
Token::Struct {
name: "Error",
len: 2
},
Token::Str("message"),
Token::Str("an error occurred"),
Token::Str("source"),
Token::None,
Token::StructEnd
]
);

let with_source = TestError {
message: "the error caused another error",
source: Some(Box::new(no_source)),
};

assert_ser_eq!(
&Serializable::new(&with_source as &(dyn Error + 'static)),
&[
Token::Struct {
name: "Error",
len: 2
},
Token::Str("message"),
Token::Str("the error caused another error"),
Token::Str("source"),
Token::Some,
Token::Struct {
name: "Error",
len: 2
},
Token::Str("message"),
Token::Str("an error occurred"),
Token::Str("source"),
Token::None,
Token::StructEnd,
Token::StructEnd
]
);
}

0 comments on commit 5624fe1

Please sign in to comment.