From c060873e18f2d59adb245fb290b35affac39ea0b Mon Sep 17 00:00:00 2001 From: Jmgr Date: Wed, 22 Oct 2025 19:53:44 +0100 Subject: [PATCH 1/3] Add support for enums containing unit variants --- src/de.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 11 deletions(-) diff --git a/src/de.rs b/src/de.rs index 961f225..dd5e539 100644 --- a/src/de.rs +++ b/src/de.rs @@ -5,7 +5,10 @@ use rquickjs::{ object::ObjectIter, qjs::{JS_GetClassID, JS_GetProperty}, }; -use serde::{de, forward_to_deserialize_any}; +use serde::{ + de::{self, value::StrDeserializer}, + forward_to_deserialize_any, +}; use crate::err::{Error, Result}; use crate::utils::{as_key, to_string_lossy}; @@ -213,14 +216,13 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } // FIXME: Replace type_of when https://github.com/DelSkayn/rquickjs/pull/458 is merged. - if get_class_id(&self.value) == ClassId::BigInt as u32 - || self.value.type_of() == rquickjs::Type::BigInt + if (get_class_id(&self.value) == ClassId::BigInt as u32 + || self.value.type_of() == rquickjs::Type::BigInt) + && let Some(f) = get_to_json(&self.value) { - if let Some(f) = get_to_json(&self.value) { - let v: Value = f.call((This(self.value.clone()),)).map_err(Error::new)?; - self.value = v; - return self.deserialize_any(visitor); - } + let v: Value = f.call((This(self.value.clone()),)).map_err(Error::new)?; + self.value = v; + return self.deserialize_any(visitor); } Err(Error::new(Exception::throw_type( @@ -255,12 +257,29 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self, _name: &'static str, _variants: &'static [&'static str], - _visitor: V, + visitor: V, ) -> Result where V: de::Visitor<'de>, { - unimplemented!() + if get_class_id(&self.value) == ClassId::String as u32 + && let Some(f) = get_to_string(&self.value) + { + let v = f.call((This(self.value.clone()),)).map_err(Error::new)?; + self.value = v; + } + + // Now require a primitive string. + let s = if self.value.is_string() { + let js_s = self.value.as_string().unwrap(); + js_s.to_string() + .unwrap_or_else(|e| to_string_lossy(self.value.ctx(), js_s, e)) + } else { + return Err(Error::new("expected a string for enum unit variant")); + }; + + // Hand Serde an EnumAccess that only supports unit variants. + visitor.visit_enum(UnitEnumAccess { variant: s }) } forward_to_deserialize_any! { @@ -532,16 +551,67 @@ fn ensure_supported(value: &Value<'_>) -> Result { )) } +/// A helper struct for deserializing enums containing unit variants. +struct UnitEnumAccess { + variant: String, +} + +impl<'de> de::EnumAccess<'de> for UnitEnumAccess { + type Error = Error; + type Variant = UnitOnlyVariant; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> + where + V: de::DeserializeSeed<'de>, + { + let id = StrDeserializer::::new(&self.variant); + let v = seed.deserialize(id)?; + Ok((v, UnitOnlyVariant)) + } +} + +struct UnitOnlyVariant; + +impl<'de> de::VariantAccess<'de> for UnitOnlyVariant { + type Error = Error; + + fn unit_variant(self) -> Result<()> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + Err(Error::new("only unit variants are supported")) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(Error::new("only unit variants are supported")) + } + + fn struct_variant(self, _fields: &'static [&'static str], _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(Error::new("only unit variants are supported")) + } +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; use rquickjs::Value; use serde::de::DeserializeOwned; + use serde::{Deserialize, Serialize}; use super::Deserializer as ValueDeserializer; - use crate::MAX_SAFE_INTEGER; use crate::test::Runtime; + use crate::{MAX_SAFE_INTEGER, from_value, to_value}; fn deserialize_value(v: Value<'_>) -> T where @@ -759,4 +829,23 @@ mod tests { assert_eq!(vec![None; 5], val); }); } + + #[test] + fn test_enum() { + let rt = Runtime::default(); + + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] + enum Test { + One, + Two, + Three, + } + + rt.context().with(|cx| { + let left = Test::Two; + let value = to_value(cx, left).unwrap(); + let right: Test = from_value(value).unwrap(); + assert_eq!(left, right); + }); + } } From aa855e11ecb333958f52a46f9c34e1c6ec0d18d4 Mon Sep 17 00:00:00 2001 From: Jmgr Date: Thu, 23 Oct 2025 19:51:11 +0100 Subject: [PATCH 2/3] Fixes after review --- src/de.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/de.rs b/src/de.rs index dd5e539..697366c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -6,7 +6,7 @@ use rquickjs::{ qjs::{JS_GetClassID, JS_GetProperty}, }; use serde::{ - de::{self, value::StrDeserializer}, + de::{self, IntoDeserializer, Unexpected}, forward_to_deserialize_any, }; @@ -270,10 +270,9 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } // Now require a primitive string. - let s = if self.value.is_string() { - let js_s = self.value.as_string().unwrap(); - js_s.to_string() - .unwrap_or_else(|e| to_string_lossy(self.value.ctx(), js_s, e)) + let s = if let Some(s) = self.value.as_string() { + s.to_string() + .unwrap_or_else(|e| to_string_lossy(self.value.ctx(), s, e)) } else { return Err(Error::new("expected a string for enum unit variant")); }; @@ -558,21 +557,18 @@ struct UnitEnumAccess { impl<'de> de::EnumAccess<'de> for UnitEnumAccess { type Error = Error; - type Variant = UnitOnlyVariant; + type Variant = Self; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> where V: de::DeserializeSeed<'de>, { - let id = StrDeserializer::::new(&self.variant); - let v = seed.deserialize(id)?; - Ok((v, UnitOnlyVariant)) + let v = seed.deserialize(self.variant.clone().into_deserializer())?; + Ok((v, self)) } } -struct UnitOnlyVariant; - -impl<'de> de::VariantAccess<'de> for UnitOnlyVariant { +impl<'de> de::VariantAccess<'de> for UnitEnumAccess { type Error = Error; fn unit_variant(self) -> Result<()> { @@ -583,21 +579,30 @@ impl<'de> de::VariantAccess<'de> for UnitOnlyVariant { where T: de::DeserializeSeed<'de>, { - Err(Error::new("only unit variants are supported")) + Err(de::Error::invalid_type( + Unexpected::NewtypeVariant, + &"unit variant", + )) } fn tuple_variant(self, _len: usize, _visitor: V) -> Result where V: de::Visitor<'de>, { - Err(Error::new("only unit variants are supported")) + Err(de::Error::invalid_type( + Unexpected::TupleVariant, + &"unit variant", + )) } fn struct_variant(self, _fields: &'static [&'static str], _visitor: V) -> Result where V: de::Visitor<'de>, { - Err(Error::new("only unit variants are supported")) + Err(de::Error::invalid_type( + Unexpected::StructVariant, + &"unit variant", + )) } } From 868d49ccda5b29cf444520853eee52d92ca96f6f Mon Sep 17 00:00:00 2001 From: Jmgr Date: Thu, 23 Oct 2025 21:14:45 +0100 Subject: [PATCH 3/3] Fix after review --- src/de.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/de.rs b/src/de.rs index 697366c..5e41e64 100644 --- a/src/de.rs +++ b/src/de.rs @@ -557,18 +557,20 @@ struct UnitEnumAccess { impl<'de> de::EnumAccess<'de> for UnitEnumAccess { type Error = Error; - type Variant = Self; + type Variant = UnitOnlyVariant; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> where V: de::DeserializeSeed<'de>, { - let v = seed.deserialize(self.variant.clone().into_deserializer())?; - Ok((v, self)) + let v = seed.deserialize(self.variant.into_deserializer())?; + Ok((v, UnitOnlyVariant)) } } -impl<'de> de::VariantAccess<'de> for UnitEnumAccess { +struct UnitOnlyVariant; + +impl<'de> de::VariantAccess<'de> for UnitOnlyVariant { type Error = Error; fn unit_variant(self) -> Result<()> {