chore(serde): support enum deserialization (#3941)

This commit is contained in:
David Knaack
2026-05-31 13:49:58 +02:00
committed by GitHub
parent f28f7791a9
commit 9a17d3a4e2
+101 -2
View File
@@ -1,6 +1,6 @@
use crate::module::ALL_MODULES;
use serde::de::{
Deserializer, Error, IntoDeserializer, Visitor,
self, Deserializer, Error, IntoDeserializer, Visitor,
value::{Error as ValueError, MapDeserializer, SeqDeserializer},
};
use std::{cmp::Ordering, fmt};
@@ -100,6 +100,20 @@ impl ValueDeserializer<'_> {
_ => ValueError::custom(msg),
}
}
/// Return the fitting `de::Unexpected` type description for the given value.
/// For use with `Error::invalid_type`.
fn serde_unexpected(&self) -> de::Unexpected<'_> {
match self.value {
ValueRef::Boolean(b) => de::Unexpected::Bool(b),
ValueRef::Integer(i) => de::Unexpected::Signed(i),
ValueRef::Float(f) => de::Unexpected::Float(f),
ValueRef::String(s) => de::Unexpected::Str(s),
ValueRef::Array(_v) => de::Unexpected::Other("array"),
ValueRef::Table(_v) => de::Unexpected::Other("table"),
ValueRef::Datetime(_v) => de::Unexpected::Other("datetime"),
}
}
}
impl<'de> IntoDeserializer<'de> for ValueDeserializer<'de> {
@@ -215,10 +229,29 @@ impl<'de> Deserializer<'de> for ValueDeserializer<'de> {
visitor.visit_newtype_struct(self)
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.value {
// de::Value::StrDeserializer implements de::EnumAccess, so we can just use it.
ValueRef::String(s) => visitor.visit_enum(s.into_deserializer()),
_ => Err(Self::Error::invalid_type(
self.serde_unexpected(),
&"string",
)),
}
}
// Handle most deserialization cases by deferring to `deserialize_any`.
serde::forward_to_deserialize_any! {
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq
bytes byte_buf map unit_struct tuple_struct enum tuple identifier
bytes byte_buf map unit_struct tuple_struct tuple identifier
}
}
@@ -350,6 +383,72 @@ mod test {
assert_eq!(result.foo.0, "bar".to_owned());
}
#[derive(Deserialize, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
enum SampleEnum {
FirstItem,
Second,
ThirdItem,
}
#[test]
fn test_deserialize_enum() {
#[derive(Deserialize)]
struct Sample {
foo: SampleEnum,
}
let value = toml::toml! {
foo = "first_item"
};
let deserializer = ValueDeserializer::new(&value);
let result = Sample::deserialize(deserializer).unwrap();
assert_eq!(result.foo, SampleEnum::FirstItem);
}
#[test]
fn test_deserialize_enum_unknown() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct Sample {
foo: SampleEnum,
}
let value = toml::toml! {
foo = "unknown"
};
let deserializer = ValueDeserializer::new(&value);
let result = Sample::deserialize(deserializer);
assert!(result.is_err());
}
#[test]
fn test_deserialize_enum_invalid_type() {
#[derive(Deserialize, PartialEq, Debug)]
#[allow(dead_code)]
struct Sample {
foo: SampleEnum,
}
let value = toml::toml! {
foo = 1
};
let deserializer = ValueDeserializer::new(&value);
let result = Sample::deserialize(deserializer);
assert_eq!(
result,
Err(serde::de::Error::custom(
"invalid type: integer `1`, expected string"
))
);
}
#[test]
fn test_deserialize_unknown() {
let value = toml::toml! {