-
Notifications
You must be signed in to change notification settings - Fork 77
Description
Hello!
I noticed you are in the process of a 1.0 stabilization.
While going through some of the changes I noticed a few things I wanted to raise attention of which may seem insignificant but I find quite important when writing a serde::de::Deserializer
-
If the format we are implementing has a direct representation for the type they are requesting then we forward the call to
deserialize_any
.For example, if we can represent all integer types
i8
i16
i32
i64
and they request ani8
and we have ani32
we should return thei32
do not try and coerce the type. -
If we cannot represent the type try coerce the value we have into the value they expect, and if we cant do that forward to
deserialize_any
For example if we only have
f64
as the only number type and they request ani32
we try convert ourf64
into ani32
or if we don’t have a number type and numbers are typically represented as strings then try parse the number from a string. -
The first two rules imply this, however for the case of being explicit. Avoid returning errors from the deserializer at all costs. Always forward to the visitor. Of course if the serialized content is invalid then that is an acceptable error, but never return an error for mismatched type vs expectations. (ie, they called
deserialize_string
and we havemap
just callvisit_map
instead of throwing an error.
-- Actual Examples from the code
Lines 473 to 490 in f24f916
fn deserialize_tuple<V>( | |
self, | |
len: usize, | |
visitor: V, | |
) -> std::result::Result<V::Value, Self::Error> | |
where | |
V: de::Visitor<'de>, | |
{ | |
if let Some(seq_len) = self.seq_length() { | |
if seq_len != len { | |
return Err(Error::custom( | |
format!("expected a tuple of length {len}, got length {seq_len}"), | |
&self.0, | |
)); | |
} | |
} | |
self.deserialize_seq(visitor) | |
} |
During deserialize_tuple
we should not do a bounds check since this isnt a "real error" (such as an invalid state or deserialization from the underlying text buffer), this is just a check that is done in code. We should just forward what we have to the visitor and then let the visitor return the error for us. Who knows, maybe the visitor is able to handle this less then full length tuple situation.
Lines 577 to 588 in f24f916
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value> | |
where | |
V: de::Visitor<'de>, | |
{ | |
if matches!(self.0, ParsedValue::NoValue | ParsedValue::Null) | |
|| matches!(self.0, ParsedValue::String(ref s) if s.is_empty()) | |
{ | |
visitor.visit_unit() | |
} else { | |
Err(Error::custom("expected unit".to_owned(), &self.0)) | |
} | |
} |
During deserialize_unit
like in the tuple function always forward to deserialize_any
(rule 1) if we dont have the type the visitor expects and we have a way of representing it in our state.
In some places this rule is upheld like such
Lines 613 to 630 in f24f916
fn deserialize_enum<V>( | |
self, | |
_name: &'static str, | |
_variants: &'static [&'static str], | |
visitor: V, | |
) -> Result<V::Value> | |
where | |
V: de::Visitor<'de>, | |
{ | |
match self.0 { | |
ParsedValue::Map(mut parsed) => visitor.visit_enum(MapDeserializer { | |
parsed: &mut parsed, | |
field_order: None, | |
popped_value: None, | |
}), | |
ParsedValue::String(s) => visitor.visit_enum(StringParsingDeserializer::new(s)?), | |
_ => self.deserialize_any(visitor), | |
} |
I propose this should always be the case since from the Deserializer's perspective the deserialize_x
functions are mealy "hints" as to what the visitor would like us to return, if we cannot convert to that type we should just give the visitor what we have and hope they can figure it out, in most cases they likely wont be able to and they will return an error, but we shouldn't impose hard limits on visitor implementations.