bevy_reflect: put serialize into external ReflectSerialize type (#4782)

builds on top of #4780 

# Objective

`Reflect` and `Serialize` are currently very tied together because `Reflect` has a `fn serialize(&self) -> Option<Serializable<'_>>` method. Because of that, we can either implement `Reflect` for types like `Option<T>` with `T: Serialize` and have `fn serialize` be implemented, or without the bound but having `fn serialize` return `None`.

By separating `ReflectSerialize` into a separate type (like how it already is for `ReflectDeserialize`, `ReflectDefault`), we could separately `.register::<Option<T>>()` and `.register_data::<Option<T>, ReflectSerialize>()` only if the type `T: Serialize`.

This PR does not change the registration but allows it to be changed in a future PR.

## Solution

- add the type
```rust
struct ReflectSerialize { .. }
impl<T: Reflect + Serialize> FromType<T> for ReflectSerialize { .. }
```

- remove `#[reflect(Serialize)]` special casing. 

- when serializing reflect value types, look for `ReflectSerialize` in the `TypeRegistry` instead of calling `value.serialize()`
This commit is contained in:
Jakob Hellermann 2022-06-20 17:18:58 +00:00
parent bb1d524833
commit 218b0fd3b6
18 changed files with 77 additions and 85 deletions

View File

@ -10,7 +10,7 @@ use crate::{
Asset, Assets, Asset, Assets,
}; };
use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize}; use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_utils::Uuid; use bevy_utils::Uuid;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_utils::AHasher; use bevy_utils::AHasher;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{

View File

@ -1,6 +1,6 @@
use bevy_derive::{Deref, DerefMut}; use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{color::Color, extract_resource::ExtractResource}; use bevy_render::{color::Color, extract_resource::ExtractResource};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -1,6 +1,6 @@
use crate::clear_color::ClearColorConfig; use crate::clear_color::ClearColorConfig;
use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{ use bevy_render::{
camera::{Camera, CameraRenderGraph, Projection}, camera::{Camera, CameraRenderGraph, Projection},
extract_component::ExtractComponent, extract_component::ExtractComponent,

View File

@ -8,6 +8,7 @@ use crate::{
}; };
use bevy_reflect::{ use bevy_reflect::{
impl_from_reflect_value, impl_reflect_value, FromType, Reflect, ReflectDeserialize, impl_from_reflect_value, impl_reflect_value, FromType, Reflect, ReflectDeserialize,
ReflectSerialize,
}; };
#[derive(Clone)] #[derive(Clone)]

View File

@ -18,7 +18,6 @@ use syn::{Meta, NestedMeta, Path};
const DEBUG_ATTR: &str = "Debug"; const DEBUG_ATTR: &str = "Debug";
const PARTIAL_EQ_ATTR: &str = "PartialEq"; const PARTIAL_EQ_ATTR: &str = "PartialEq";
const HASH_ATTR: &str = "Hash"; const HASH_ATTR: &str = "Hash";
const SERIALIZE_ATTR: &str = "Serialize";
// The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax) // The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax)
// but useful to know exist nonetheless // but useful to know exist nonetheless
@ -54,7 +53,6 @@ impl Default for TraitImpl {
/// * `Debug` /// * `Debug`
/// * `Hash` /// * `Hash`
/// * `PartialEq` /// * `PartialEq`
/// * `Serialize`
/// ///
/// When registering a trait, there are a few things to keep in mind: /// When registering a trait, there are a few things to keep in mind:
/// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default` /// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default`
@ -110,7 +108,6 @@ pub(crate) struct ReflectTraits {
debug: TraitImpl, debug: TraitImpl,
hash: TraitImpl, hash: TraitImpl,
partial_eq: TraitImpl, partial_eq: TraitImpl,
serialize: TraitImpl,
idents: Vec<Ident>, idents: Vec<Ident>,
} }
@ -133,7 +130,6 @@ impl ReflectTraits {
DEBUG_ATTR => traits.debug = TraitImpl::Implemented, DEBUG_ATTR => traits.debug = TraitImpl::Implemented,
PARTIAL_EQ_ATTR => traits.partial_eq = TraitImpl::Implemented, PARTIAL_EQ_ATTR => traits.partial_eq = TraitImpl::Implemented,
HASH_ATTR => traits.hash = TraitImpl::Implemented, HASH_ATTR => traits.hash = TraitImpl::Implemented,
SERIALIZE_ATTR => traits.serialize = TraitImpl::Implemented,
// We only track reflected idents for traits not considered special // We only track reflected idents for traits not considered special
_ => traits.idents.push(utility::get_reflect_ident(&ident)), _ => traits.idents.push(utility::get_reflect_ident(&ident)),
} }
@ -156,7 +152,6 @@ impl ReflectTraits {
DEBUG_ATTR => traits.debug = trait_func_ident, DEBUG_ATTR => traits.debug = trait_func_ident,
PARTIAL_EQ_ATTR => traits.partial_eq = trait_func_ident, PARTIAL_EQ_ATTR => traits.partial_eq = trait_func_ident,
HASH_ATTR => traits.hash = trait_func_ident, HASH_ATTR => traits.hash = trait_func_ident,
SERIALIZE_ATTR => traits.serialize = trait_func_ident,
_ => {} _ => {}
} }
} }
@ -230,25 +225,6 @@ impl ReflectTraits {
} }
} }
/// Returns the implementation of `Reflect::serializable` as a `TokenStream`.
///
/// If `Serialize` was not registered, returns `None`.
pub fn get_serialize_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {
match &self.serialize {
TraitImpl::Implemented => Some(quote! {
fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> {
Some(#bevy_reflect_path::serde::Serializable::Borrowed(self))
}
}),
TraitImpl::Custom(impl_fn) => Some(quote! {
fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> {
Some(#impl_fn(self))
}
}),
TraitImpl::NotImplemented => None,
}
}
/// Returns the implementation of `Reflect::debug` as a `TokenStream`. /// Returns the implementation of `Reflect::debug` as a `TokenStream`.
/// ///
/// If `Debug` was not registered, returns `None`. /// If `Debug` was not registered, returns `None`.

View File

@ -40,7 +40,6 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
let field_indices = (0..field_count).collect::<Vec<usize>>(); let field_indices = (0..field_count).collect::<Vec<usize>>();
let hash_fn = derive_data.traits().get_hash_impl(bevy_reflect_path); let hash_fn = derive_data.traits().get_hash_impl(bevy_reflect_path);
let serialize_fn = derive_data.traits().get_serialize_impl(bevy_reflect_path);
let partial_eq_fn = derive_data let partial_eq_fn = derive_data
.traits() .traits()
.get_partial_eq_impl(bevy_reflect_path) .get_partial_eq_impl(bevy_reflect_path)
@ -192,8 +191,6 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
#partial_eq_fn #partial_eq_fn
#debug_fn #debug_fn
#serialize_fn
} }
}) })
} }
@ -216,7 +213,6 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
let field_indices = (0..field_count).collect::<Vec<usize>>(); let field_indices = (0..field_count).collect::<Vec<usize>>();
let hash_fn = derive_data.traits().get_hash_impl(bevy_reflect_path); let hash_fn = derive_data.traits().get_hash_impl(bevy_reflect_path);
let serialize_fn = derive_data.traits().get_serialize_impl(bevy_reflect_path);
let partial_eq_fn = derive_data let partial_eq_fn = derive_data
.traits() .traits()
.get_partial_eq_impl(bevy_reflect_path) .get_partial_eq_impl(bevy_reflect_path)
@ -344,8 +340,6 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
#partial_eq_fn #partial_eq_fn
#debug_fn #debug_fn
#serialize_fn
} }
}) })
} }
@ -359,7 +353,6 @@ pub(crate) fn impl_value(
reflect_traits: &ReflectTraits, reflect_traits: &ReflectTraits,
) -> TokenStream { ) -> TokenStream {
let hash_fn = reflect_traits.get_hash_impl(bevy_reflect_path); let hash_fn = reflect_traits.get_hash_impl(bevy_reflect_path);
let serialize_fn = reflect_traits.get_serialize_impl(bevy_reflect_path);
let partial_eq_fn = reflect_traits.get_partial_eq_impl(bevy_reflect_path); let partial_eq_fn = reflect_traits.get_partial_eq_impl(bevy_reflect_path);
let debug_fn = reflect_traits.get_debug_impl(); let debug_fn = reflect_traits.get_debug_impl();
@ -445,8 +438,6 @@ pub(crate) fn impl_value(
#partial_eq_fn #partial_eq_fn
#debug_fn #debug_fn
#serialize_fn
} }
}) })
} }

View File

@ -1,6 +1,5 @@
use crate::{ use crate::{
serde::Serializable, utility::NonGenericTypeInfoCell, DynamicInfo, Reflect, ReflectMut, utility::NonGenericTypeInfoCell, DynamicInfo, Reflect, ReflectMut, ReflectRef, TypeInfo, Typed,
ReflectRef, TypeInfo, Typed,
}; };
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
@ -217,10 +216,6 @@ unsafe impl Reflect for DynamicArray {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
array_partial_eq(self, value) array_partial_eq(self, value)
} }
fn serializable(&self) -> Option<Serializable> {
None
}
} }
impl Array for DynamicArray { impl Array for DynamicArray {

View File

@ -1,7 +1,7 @@
use crate as bevy_reflect; use crate as bevy_reflect;
use crate::prelude::ReflectDefault; use crate::prelude::ReflectDefault;
use crate::reflect::Reflect; use crate::reflect::Reflect;
use crate::ReflectDeserialize; use crate::{ReflectDeserialize, ReflectSerialize};
use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_reflect_value}; use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_reflect_value};
use glam::*; use glam::*;

View File

@ -1,8 +1,8 @@
use crate as bevy_reflect; use crate as bevy_reflect;
use crate::{ use crate::{
map_partial_eq, serde::Serializable, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect, FromType,
FromType, GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter, Reflect, GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter, Reflect, ReflectDeserialize,
ReflectDeserialize, ReflectMut, ReflectRef, TypeInfo, TypeRegistration, Typed, ValueInfo, ReflectMut, ReflectRef, ReflectSerialize, TypeInfo, TypeRegistration, Typed, ValueInfo,
}; };
use crate::utility::{GenericTypeInfoCell, NonGenericTypeInfoCell}; use crate::utility::{GenericTypeInfoCell, NonGenericTypeInfoCell};
@ -159,10 +159,6 @@ unsafe impl<T: FromReflect> Reflect for Vec<T> {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
crate::list_partial_eq(self, value) crate::list_partial_eq(self, value)
} }
fn serializable(&self) -> Option<Serializable> {
None
}
} }
impl<T: FromReflect> Typed for Vec<T> { impl<T: FromReflect> Typed for Vec<T> {
@ -420,11 +416,6 @@ unsafe impl<T: Reflect, const N: usize> Reflect for [T; N] {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
crate::array_partial_eq(self, value) crate::array_partial_eq(self, value)
} }
#[inline]
fn serializable(&self) -> Option<Serializable> {
None
}
} }
impl<T: FromReflect, const N: usize> FromReflect for [T; N] { impl<T: FromReflect, const N: usize> FromReflect for [T; N] {
@ -541,10 +532,6 @@ unsafe impl Reflect for Cow<'static, str> {
Some(false) Some(false)
} }
} }
fn serializable(&self) -> Option<Serializable> {
Some(Serializable::Borrowed(self))
}
} }
impl Typed for Cow<'static, str> { impl Typed for Cow<'static, str> {
@ -558,6 +545,7 @@ impl GetTypeRegistration for Cow<'static, str> {
fn get_type_registration() -> TypeRegistration { fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Cow<'static, str>>(); let mut registration = TypeRegistration::of::<Cow<'static, str>>();
registration.insert::<ReflectDeserialize>(FromType::<Cow<'static, str>>::from_type()); registration.insert::<ReflectDeserialize>(FromType::<Cow<'static, str>>::from_type());
registration.insert::<ReflectSerialize>(FromType::<Cow<'static, str>>::from_type());
registration registration
} }
} }
@ -570,13 +558,19 @@ impl FromReflect for Cow<'static, str> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::Reflect; use crate::{Reflect, ReflectSerialize, TypeRegistry};
use bevy_utils::HashMap; use bevy_utils::HashMap;
use std::f32::consts::{PI, TAU}; use std::f32::consts::{PI, TAU};
#[test] #[test]
fn can_serialize_duration() { fn can_serialize_duration() {
assert!(std::time::Duration::ZERO.serializable().is_some()); let mut type_registry = TypeRegistry::default();
type_registry.register::<std::time::Duration>();
let reflect_serialize = type_registry
.get_type_data::<ReflectSerialize>(std::any::TypeId::of::<std::time::Duration>())
.unwrap();
let _serializable = reflect_serialize.get_serializable(&std::time::Duration::ZERO);
} }
#[test] #[test]

View File

@ -34,8 +34,8 @@ pub mod prelude {
pub use crate::std_traits::*; pub use crate::std_traits::*;
#[doc(hidden)] #[doc(hidden)]
pub use crate::{ pub use crate::{
reflect_trait, GetField, GetTupleStructField, Reflect, ReflectDeserialize, Struct, reflect_trait, GetField, GetTupleStructField, Reflect, ReflectDeserialize,
TupleStruct, ReflectSerialize, Struct, TupleStruct,
}; };
} }
@ -877,7 +877,8 @@ bevy_reflect::tests::should_reflect_debug::Test {
let v = vec3(12.0, 3.0, -6.9); let v = vec3(12.0, 3.0, -6.9);
let mut registry = TypeRegistry::default(); let mut registry = TypeRegistry::default();
registry.add_registration(Vec3::get_type_registration()); registry.register::<f32>();
registry.register::<Vec3>();
let ser = ReflectSerializer::new(&v, &registry); let ser = ReflectSerializer::new(&v, &registry);

View File

@ -3,8 +3,8 @@ use std::fmt::{Debug, Formatter};
use crate::utility::NonGenericTypeInfoCell; use crate::utility::NonGenericTypeInfoCell;
use crate::{ use crate::{
serde::Serializable, Array, ArrayIter, DynamicArray, DynamicInfo, FromReflect, Reflect, Array, ArrayIter, DynamicArray, DynamicInfo, FromReflect, Reflect, ReflectMut, ReflectRef,
ReflectMut, ReflectRef, TypeInfo, Typed, TypeInfo, Typed,
}; };
/// An ordered, mutable list of [Reflect] items. This corresponds to types like [`std::vec::Vec`]. /// An ordered, mutable list of [Reflect] items. This corresponds to types like [`std::vec::Vec`].
@ -229,10 +229,6 @@ unsafe impl Reflect for DynamicList {
list_partial_eq(self, value) list_partial_eq(self, value)
} }
fn serializable(&self) -> Option<Serializable> {
None
}
fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "DynamicList(")?; write!(f, "DynamicList(")?;
list_debug(self, f)?; list_debug(self, f)?;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
serde::type_fields, Array, List, Map, Reflect, ReflectRef, Struct, Tuple, TupleStruct, serde::type_fields, Array, List, Map, Reflect, ReflectRef, ReflectSerialize, Struct, Tuple,
TypeRegistry, TupleStruct, TypeRegistry,
}; };
use serde::{ use serde::{
ser::{SerializeMap, SerializeSeq}, ser::{SerializeMap, SerializeSeq},
@ -22,13 +22,19 @@ impl<'a> Serializable<'a> {
} }
} }
fn get_serializable<E: serde::ser::Error>(reflect_value: &dyn Reflect) -> Result<Serializable, E> { fn get_serializable<'a, E: serde::ser::Error>(
reflect_value.serializable().ok_or_else(|| { reflect_value: &'a dyn Reflect,
serde::ser::Error::custom(format_args!( type_registry: &TypeRegistry,
"Type '{}' does not support ReflectValue serialization", ) -> Result<Serializable<'a>, E> {
reflect_value.type_name() let reflect_serialize = type_registry
)) .get_type_data::<ReflectSerialize>(reflect_value.type_id())
}) .ok_or_else(|| {
serde::ser::Error::custom(format_args!(
"Type '{}' did not register ReflectSerialize",
reflect_value.type_name()
))
})?;
Ok(reflect_serialize.get_serializable(reflect_value))
} }
pub struct ReflectSerializer<'a> { pub struct ReflectSerializer<'a> {
@ -101,7 +107,7 @@ impl<'a> Serialize for ReflectValueSerializer<'a> {
state.serialize_entry(type_fields::TYPE, self.value.type_name())?; state.serialize_entry(type_fields::TYPE, self.value.type_name())?;
state.serialize_entry( state.serialize_entry(
type_fields::VALUE, type_fields::VALUE,
get_serializable::<S::Error>(self.value)?.borrow(), get_serializable::<S::Error>(self.value, self.registry)?.borrow(),
)?; )?;
state.end() state.end()
} }

View File

@ -1,3 +1,4 @@
use crate::serde::Serializable;
use crate::{Reflect, TypeInfo, Typed}; use crate::{Reflect, TypeInfo, Typed};
use bevy_utils::{HashMap, HashSet}; use bevy_utils::{HashMap, HashSet};
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
@ -389,6 +390,35 @@ pub trait FromType<T> {
fn from_type() -> Self; fn from_type() -> Self;
} }
/// A struct used to serialize reflected instances of a type.
///
/// A `ReflectSerialize` for type `T` can be obtained via
/// [`FromType::from_type`].
#[derive(Clone)]
pub struct ReflectSerialize {
get_serializable: for<'a> fn(value: &'a dyn Reflect) -> Serializable,
}
impl<T: Reflect + erased_serde::Serialize> FromType<T> for ReflectSerialize {
fn from_type() -> Self {
ReflectSerialize {
get_serializable: |value| {
let value = value.downcast_ref::<T>().unwrap_or_else(|| {
panic!("ReflectSerialize::get_serialize called with type `{}`, even though it was created for `{}`", value.type_name(), std::any::type_name::<T>())
});
Serializable::Borrowed(value)
},
}
}
}
impl ReflectSerialize {
/// Turn the value into a serializable representation
pub fn get_serializable<'a>(&self, value: &'a dyn Reflect) -> Serializable<'a> {
(self.get_serializable)(value)
}
}
/// A struct used to deserialize reflected instances of a type. /// A struct used to deserialize reflected instances of a type.
/// ///
/// A `ReflectDeserialize` for type `T` can be obtained via /// A `ReflectDeserialize` for type `T` can be obtained via

View File

@ -4,7 +4,9 @@ use super::DepthCalculation;
use bevy_app::{App, CoreStage, Plugin, StartupStage}; use bevy_app::{App, CoreStage, Plugin, StartupStage};
use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_math::Mat4; use bevy_math::Mat4;
use bevy_reflect::{std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize}; use bevy_reflect::{
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
};
use bevy_window::ModifiesWindows; use bevy_window::ModifiesWindows;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -4,7 +4,7 @@ pub use colorspace::*;
use crate::color::{HslRepresentation, SrgbColorSpace}; use crate::color::{HslRepresentation, SrgbColorSpace};
use bevy_math::{Vec3, Vec4}; use bevy_math::{Vec3, Vec4};
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize}; use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize, ReflectSerialize};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::{Add, AddAssign, Mul, MulAssign}; use std::ops::{Add, AddAssign, Mul, MulAssign};
use thiserror::Error; use thiserror::Error;

View File

@ -7,7 +7,7 @@ use bevy_ecs::{
}; };
use bevy_input::{mouse::MouseButton, touch::Touches, Input}; use bevy_input::{mouse::MouseButton, touch::Touches, Input};
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd; use bevy_utils::FloatOrd;
use bevy_window::Windows; use bevy_window::Windows;

View File

@ -7,7 +7,7 @@ use bevy_ecs::{
reflect::ReflectComponent, reflect::ReflectComponent,
system::{Query, Res}, system::{Query, Res},
}; };
use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::texture::Image; use bevy_render::texture::Image;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};