This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 7cd451561 feat(Rust): support Option in MetaFieldType se/de (#2528)
7cd451561 is described below

commit 7cd451561ac8e79d2c8af000f71d796bc77b006c
Author: urlyy <[email protected]>
AuthorDate: Tue Aug 26 17:28:21 2025 +0800

    feat(Rust): support Option in MetaFieldType se/de (#2528)
    
    ## Why?
    Rust does not support objects being `null`; instead, it uses the
    `Option` type to wrap nullable objects. In the current Rust macros,
    `Option<T>` is extracted as `MetaFieldType { name: "Option", generics:
    [T] }`, and then compared with the `MetaFieldType { name: T, generics:
    [] }` received from the sender peer like java. This causes a type
    mismatch. Therefore, the `Option` type requires special handling.
    
    ## What does this PR do?
    - support `Option` in MetaFieldType se/de. Specifically, during Rust
    peer serialization, I will serialize `Option<T>` as `(T::type_id,
    nullable=true)`. During deserialization, `(T::type_id, nullable=true)`
    will be converted to `Option<T>`, while `(T::type_id, nullable=false)`
    will be converted to `T`.
    - correct the previous mistake in #2492, rename `FieldId` to `TypeId`.
    
    ## notice
    It doesn’t support handling adjacent Options, such as Option<Option<T>>.
    
    ## Related issues
    #2526
    
    ## Does this PR introduce any user-facing change?
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 rust/fory-core/src/meta/type_meta.rs            | 74 +++++++++++++++----------
 rust/fory-core/src/resolver/type_resolver.rs    | 22 ++++----
 rust/fory-core/src/serializer/any.rs            |  4 +-
 rust/fory-core/src/serializer/bool.rs           |  4 +-
 rust/fory-core/src/serializer/datetime.rs       |  6 +-
 rust/fory-core/src/serializer/list.rs           |  4 +-
 rust/fory-core/src/serializer/map.rs            |  4 +-
 rust/fory-core/src/serializer/number.rs         | 14 ++---
 rust/fory-core/src/serializer/primitive_list.rs | 16 +++---
 rust/fory-core/src/serializer/set.rs            |  4 +-
 rust/fory-core/src/serializer/string.rs         |  4 +-
 rust/fory-core/src/types.rs                     |  4 +-
 rust/fory-derive/src/object/read.rs             |  2 +-
 rust/fory-derive/src/object/util.rs             | 13 ++++-
 rust/tests/tests/test_compatible.rs             | 21 ++++---
 15 files changed, 116 insertions(+), 80 deletions(-)

diff --git a/rust/fory-core/src/meta/type_meta.rs 
b/rust/fory-core/src/meta/type_meta.rs
index e60af000f..eca526511 100644
--- a/rust/fory-core/src/meta/type_meta.rs
+++ b/rust/fory-core/src/meta/type_meta.rs
@@ -20,7 +20,7 @@ use crate::buffer::{Reader, Writer};
 use crate::error::Error;
 use crate::meta::murmurhash3_x64_128;
 use crate::meta::{Encoding, MetaStringDecoder};
-use crate::types::FieldId;
+use crate::types::TypeId;
 use anyhow::anyhow;
 use std::cmp::min;
 
@@ -50,26 +50,33 @@ impl FieldType {
         FieldType { type_id, generics }
     }
 
-    fn to_bytes(&self, writer: &mut Writer, write_flag: bool) -> Result<(), 
Error> {
+    fn to_bytes(&self, writer: &mut Writer, write_flag: bool, nullable: bool) 
-> Result<(), Error> {
+        if self.type_id == TypeId::ForyOption.into() {
+            self.generics
+                .first()
+                .unwrap()
+                .to_bytes(writer, write_flag, true)?;
+            return Ok(());
+        }
         let mut header: i32 = self.type_id as i32;
-        // let ref_tracking = false;
-        // todo if "Option<T>" is nullability then T nullability=true
-        // let nullability = false;
         if write_flag {
             header <<= 2;
+            // let ref_tracking = false;
+            if nullable {
+                header |= 2;
+            }
         }
         writer.var_int32(header);
-
         match self.type_id {
-            x if x == FieldId::ARRAY as i16 || x == FieldId::SET as i16 => {
+            x if x == TypeId::ARRAY as i16 || x == TypeId::SET as i16 => {
                 let generic = self.generics.first().unwrap();
-                generic.to_bytes(writer, true)?;
+                generic.to_bytes(writer, true, false)?;
             }
-            x if x == FieldId::MAP as i16 => {
+            x if x == TypeId::MAP as i16 => {
                 let key_generic = self.generics.first().unwrap();
                 let val_generic = self.generics.get(1).unwrap();
-                key_generic.to_bytes(writer, true)?;
-                val_generic.to_bytes(writer, true)?;
+                key_generic.to_bytes(writer, true, false)?;
+                val_generic.to_bytes(writer, true, false)?;
             }
             _ => {}
         }
@@ -77,28 +84,29 @@ impl FieldType {
     }
 
     #[allow(clippy::needless_late_init)]
-    fn from_bytes(reader: &mut Reader, read_flag: bool) -> Self {
+    fn from_bytes(reader: &mut Reader, read_flag: bool, nullable: 
Option<bool>) -> Self {
         let header = reader.var_int32();
         let type_id;
+        let _nullable;
         if read_flag {
             type_id = (header >> 2) as i16;
             // let tracking_ref = (header & 1) != 0;
-            // todo if T is nullability then "Option<T>"
-            // let nullable = (header & 2) != 0;
+            _nullable = (header & 2) != 0;
         } else {
             type_id = header as i16;
+            _nullable = nullable.unwrap();
         }
-        match type_id {
-            x if x == FieldId::ARRAY as i16 || x == FieldId::SET as i16 => {
-                let generic = Self::from_bytes(reader, true);
+        let field_type = match type_id {
+            x if x == TypeId::ARRAY.into() || x == TypeId::SET.into() => {
+                let generic = Self::from_bytes(reader, true, None);
                 Self {
                     type_id,
                     generics: vec![generic],
                 }
             }
-            x if x == FieldId::MAP as i16 => {
-                let key_generic = Self::from_bytes(reader, true);
-                let val_generic = Self::from_bytes(reader, true);
+            x if x == TypeId::MAP.into() => {
+                let key_generic = Self::from_bytes(reader, true, None);
+                let val_generic = Self::from_bytes(reader, true, None);
                 Self {
                     type_id,
                     generics: vec![key_generic, val_generic],
@@ -108,6 +116,14 @@ impl FieldType {
                 type_id,
                 generics: vec![],
             },
+        };
+        if _nullable {
+            Self {
+                type_id: TypeId::ForyOption.into(),
+                generics: vec![field_type],
+            }
+        } else {
+            field_type
         }
     }
 }
@@ -139,7 +155,7 @@ impl FieldInfo {
 
     pub fn from_bytes(reader: &mut Reader) -> FieldInfo {
         let header = reader.u8();
-        // let nullability = (header & 2) != 0;
+        let nullable = (header & 2) != 0;
         // let ref_tracking = (header & 1) != 0;
         let encoding = Self::u8_to_encoding((header >> 6) & 0b11).unwrap();
         let mut name_size = ((header >> 2) & FIELD_NAME_SIZE_THRESHOLD as u8) 
as usize;
@@ -148,7 +164,7 @@ impl FieldInfo {
         }
         name_size += 1;
 
-        let field_type = FieldType::from_bytes(reader, false);
+        let field_type = FieldType::from_bytes(reader, false, 
Option::from(nullable));
 
         let field_name_bytes = reader.bytes(name_size);
 
@@ -171,12 +187,12 @@ impl FieldInfo {
         let name_encoded = meta_string.bytes.as_slice();
         let name_size = name_encoded.len() - 1;
         let mut header: u8 = (min(FIELD_NAME_SIZE_THRESHOLD, name_size) as u8) 
<< 2;
-        let ref_tracking = false;
-        let nullability = false;
-        if ref_tracking {
-            header |= 1;
-        }
-        if nullability {
+        // let ref_tracking = false;
+        let nullable = self.field_type.type_id == TypeId::ForyOption.into();
+        // if ref_tracking {
+        //     header |= 1;
+        // }
+        if nullable {
             header |= 2;
         }
         let encoding_idx = ENCODING_OPTIONS
@@ -188,7 +204,7 @@ impl FieldInfo {
         if name_size >= FIELD_NAME_SIZE_THRESHOLD {
             writer.var_int32((name_size - FIELD_NAME_SIZE_THRESHOLD) as i32);
         }
-        self.field_type.to_bytes(&mut writer, false)?;
+        self.field_type.to_bytes(&mut writer, false, nullable)?;
         // write field_name
         writer.bytes(name_encoded);
         Ok(writer.dump())
diff --git a/rust/fory-core/src/resolver/type_resolver.rs 
b/rust/fory-core/src/resolver/type_resolver.rs
index de8379da4..eea1f347f 100644
--- a/rust/fory-core/src/resolver/type_resolver.rs
+++ b/rust/fory-core/src/resolver/type_resolver.rs
@@ -19,7 +19,7 @@ use super::context::{ReadContext, WriteContext};
 use crate::error::Error;
 use crate::fory::Fory;
 use crate::serializer::{Serializer, StructSerializer};
-use crate::types::FieldId;
+use crate::types::TypeId;
 use chrono::{NaiveDate, NaiveDateTime};
 use std::{any::Any, collections::HashMap};
 
@@ -100,18 +100,18 @@ impl Default for TypeResolver {
     fn default() -> Self {
         let mut serialize_map = HashMap::new();
 
-        register_harness!(bool, FieldId::BOOL, serialize_map);
-        register_harness!(i8, FieldId::INT8, serialize_map);
-        register_harness!(i16, FieldId::INT16, serialize_map);
-        register_harness!(i32, FieldId::INT32, serialize_map);
-        register_harness!(i64, FieldId::INT64, serialize_map);
-        register_harness!(f32, FieldId::FLOAT32, serialize_map);
-        register_harness!(f64, FieldId::FLOAT64, serialize_map);
+        register_harness!(bool, TypeId::BOOL, serialize_map);
+        register_harness!(i8, TypeId::INT8, serialize_map);
+        register_harness!(i16, TypeId::INT16, serialize_map);
+        register_harness!(i32, TypeId::INT32, serialize_map);
+        register_harness!(i64, TypeId::INT64, serialize_map);
+        register_harness!(f32, TypeId::FLOAT32, serialize_map);
+        register_harness!(f64, TypeId::FLOAT64, serialize_map);
 
-        register_harness!(String, FieldId::STRING, serialize_map);
+        register_harness!(String, TypeId::STRING, serialize_map);
 
-        register_harness!(NaiveDate, FieldId::LOCAL_DATE, serialize_map);
-        register_harness!(NaiveDateTime, FieldId::TIMESTAMP, serialize_map);
+        register_harness!(NaiveDate, TypeId::LOCAL_DATE, serialize_map);
+        register_harness!(NaiveDateTime, TypeId::TIMESTAMP, serialize_map);
 
         TypeResolver {
             serialize_map,
diff --git a/rust/fory-core/src/serializer/any.rs 
b/rust/fory-core/src/serializer/any.rs
index c6cc1023a..cf9ab52ce 100644
--- a/rust/fory-core/src/serializer/any.rs
+++ b/rust/fory-core/src/serializer/any.rs
@@ -19,7 +19,7 @@ use crate::error::Error;
 use crate::fory::Fory;
 use crate::resolver::context::{ReadContext, WriteContext};
 use crate::serializer::Serializer;
-use crate::types::{FieldId, Mode, RefFlag};
+use crate::types::{Mode, RefFlag, TypeId};
 use anyhow::anyhow;
 use std::any::Any;
 
@@ -37,7 +37,7 @@ impl Serializer for Box<dyn Any> {
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::ForyTypeTag.into()
+        TypeId::ForyTypeTag.into()
     }
 
     fn serialize(&self, context: &mut WriteContext) {
diff --git a/rust/fory-core/src/serializer/bool.rs 
b/rust/fory-core/src/serializer/bool.rs
index 0586cea57..1fb0f4f5b 100644
--- a/rust/fory-core/src/serializer/bool.rs
+++ b/rust/fory-core/src/serializer/bool.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::FieldId;
+use crate::types::TypeId;
 use std::mem;
 
 impl Serializer for bool {
@@ -37,6 +37,6 @@ impl Serializer for bool {
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::BOOL.into()
+        TypeId::BOOL.into()
     }
 }
diff --git a/rust/fory-core/src/serializer/datetime.rs 
b/rust/fory-core/src/serializer/datetime.rs
index 5a1248804..8084ffd44 100644
--- a/rust/fory-core/src/serializer/datetime.rs
+++ b/rust/fory-core/src/serializer/datetime.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::{FieldId, ForyGeneralList};
+use crate::types::{ForyGeneralList, TypeId};
 use crate::util::EPOCH;
 use anyhow::anyhow;
 use chrono::{DateTime, Days, NaiveDate, NaiveDateTime};
@@ -45,7 +45,7 @@ impl Serializer for NaiveDateTime {
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::TIMESTAMP.into()
+        TypeId::TIMESTAMP.into()
     }
 }
 
@@ -71,7 +71,7 @@ impl Serializer for NaiveDate {
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::LOCAL_DATE.into()
+        TypeId::LOCAL_DATE.into()
     }
 }
 
diff --git a/rust/fory-core/src/serializer/list.rs 
b/rust/fory-core/src/serializer/list.rs
index a831adc4e..43490aeb6 100644
--- a/rust/fory-core/src/serializer/list.rs
+++ b/rust/fory-core/src/serializer/list.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::{FieldId, ForyGeneralList, SIZE_OF_REF_AND_TYPE};
+use crate::types::{ForyGeneralList, TypeId, SIZE_OF_REF_AND_TYPE};
 use std::mem;
 
 impl<T> Serializer for Vec<T>
@@ -51,7 +51,7 @@ where
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::ARRAY.into()
+        TypeId::ARRAY.into()
     }
 }
 
diff --git a/rust/fory-core/src/serializer/map.rs 
b/rust/fory-core/src/serializer/map.rs
index 75502feeb..6f00bddde 100644
--- a/rust/fory-core/src/serializer/map.rs
+++ b/rust/fory-core/src/serializer/map.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::{FieldId, ForyGeneralList, SIZE_OF_REF_AND_TYPE};
+use crate::types::{ForyGeneralList, TypeId, SIZE_OF_REF_AND_TYPE};
 use std::collections::HashMap;
 use std::mem;
 
@@ -57,7 +57,7 @@ impl<T1: Serializer + Eq + std::hash::Hash, T2: Serializer> 
Serializer for HashM
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::MAP.into()
+        TypeId::MAP.into()
     }
 }
 
diff --git a/rust/fory-core/src/serializer/number.rs 
b/rust/fory-core/src/serializer/number.rs
index 1f6b9d1f7..2d637e376 100644
--- a/rust/fory-core/src/serializer/number.rs
+++ b/rust/fory-core/src/serializer/number.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::{FieldId, ForyGeneralList};
+use crate::types::{ForyGeneralList, TypeId};
 
 macro_rules! impl_num_serializer {
     ($name: ident, $ty:tt, $field_type: expr) => {
@@ -48,9 +48,9 @@ impl ForyGeneralList for u16 {}
 impl ForyGeneralList for u32 {}
 impl ForyGeneralList for u64 {}
 
-impl_num_serializer!(i8, i8, FieldId::INT8);
-impl_num_serializer!(i16, i16, FieldId::INT16);
-impl_num_serializer!(i32, i32, FieldId::INT32);
-impl_num_serializer!(i64, i64, FieldId::INT64);
-impl_num_serializer!(f32, f32, FieldId::FLOAT32);
-impl_num_serializer!(f64, f64, FieldId::FLOAT64);
+impl_num_serializer!(i8, i8, TypeId::INT8);
+impl_num_serializer!(i16, i16, TypeId::INT16);
+impl_num_serializer!(i32, i32, TypeId::INT32);
+impl_num_serializer!(i64, i64, TypeId::INT64);
+impl_num_serializer!(f32, f32, TypeId::FLOAT32);
+impl_num_serializer!(f64, f64, TypeId::FLOAT64);
diff --git a/rust/fory-core/src/serializer/primitive_list.rs 
b/rust/fory-core/src/serializer/primitive_list.rs
index cd198aa18..0eeb7cf51 100644
--- a/rust/fory-core/src/serializer/primitive_list.rs
+++ b/rust/fory-core/src/serializer/primitive_list.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::FieldId;
+use crate::types::TypeId;
 use std::mem;
 
 pub fn to_u8_slice<T>(slice: &[T]) -> &[u8] {
@@ -78,7 +78,7 @@ impl Serializer for Vec<bool> {
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::ForyPrimitiveBoolArray.into()
+        TypeId::ForyPrimitiveBoolArray.into()
     }
 
     fn read(context: &mut ReadContext) -> Result<Self, Error> {
@@ -88,9 +88,9 @@ impl Serializer for Vec<bool> {
     }
 }
 
-impl_primitive_vec!(u8, u8, FieldId::BINARY);
-impl_primitive_vec!(i16, i16, FieldId::ForyPrimitiveShortArray);
-impl_primitive_vec!(i32, i32, FieldId::ForyPrimitiveIntArray);
-impl_primitive_vec!(i64, i64, FieldId::ForyPrimitiveLongArray);
-impl_primitive_vec!(f32, f32, FieldId::ForyPrimitiveFloatArray);
-impl_primitive_vec!(f64, f64, FieldId::ForyPrimitiveDoubleArray);
+impl_primitive_vec!(u8, u8, TypeId::BINARY);
+impl_primitive_vec!(i16, i16, TypeId::ForyPrimitiveShortArray);
+impl_primitive_vec!(i32, i32, TypeId::ForyPrimitiveIntArray);
+impl_primitive_vec!(i64, i64, TypeId::ForyPrimitiveLongArray);
+impl_primitive_vec!(f32, f32, TypeId::ForyPrimitiveFloatArray);
+impl_primitive_vec!(f64, f64, TypeId::ForyPrimitiveDoubleArray);
diff --git a/rust/fory-core/src/serializer/set.rs 
b/rust/fory-core/src/serializer/set.rs
index 04af9efd5..6140e5478 100644
--- a/rust/fory-core/src/serializer/set.rs
+++ b/rust/fory-core/src/serializer/set.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::{FieldId, ForyGeneralList, SIZE_OF_REF_AND_TYPE};
+use crate::types::{ForyGeneralList, TypeId, SIZE_OF_REF_AND_TYPE};
 use std::collections::HashSet;
 use std::mem;
 
@@ -52,7 +52,7 @@ impl<T: Serializer + Eq + std::hash::Hash> Serializer for 
HashSet<T> {
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::SET.into()
+        TypeId::SET.into()
     }
 }
 
diff --git a/rust/fory-core/src/serializer/string.rs 
b/rust/fory-core/src/serializer/string.rs
index 3b8a90f72..5c5c3d454 100644
--- a/rust/fory-core/src/serializer/string.rs
+++ b/rust/fory-core/src/serializer/string.rs
@@ -20,7 +20,7 @@ use crate::fory::Fory;
 use crate::resolver::context::ReadContext;
 use crate::resolver::context::WriteContext;
 use crate::serializer::Serializer;
-use crate::types::{FieldId, ForyGeneralList};
+use crate::types::{ForyGeneralList, TypeId};
 use std::mem;
 
 impl Serializer for String {
@@ -39,7 +39,7 @@ impl Serializer for String {
     }
 
     fn get_type_id(_fory: &Fory) -> i16 {
-        FieldId::STRING.into()
+        TypeId::STRING.into()
     }
 }
 
diff --git a/rust/fory-core/src/types.rs b/rust/fory-core/src/types.rs
index 0f0d2f701..f8bddebdc 100644
--- a/rust/fory-core/src/types.rs
+++ b/rust/fory-core/src/types.rs
@@ -42,7 +42,7 @@ pub enum RefFlag {
 #[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
 #[allow(non_camel_case_types)]
 #[repr(i16)]
-pub enum FieldId {
+pub enum TypeId {
     BOOL = 1,
     INT8 = 2,
     INT16 = 3,
@@ -91,6 +91,8 @@ pub enum FieldId {
     ForyPrimitiveFloatArray = 262,
     ForyPrimitiveDoubleArray = 263,
     ForyStringArray = 264,
+    // only used at receiver peer
+    ForyOption = 265,
 }
 
 pub trait ForyGeneralList {}
diff --git a/rust/fory-derive/src/object/read.rs 
b/rust/fory-derive/src/object/read.rs
index 71be7670d..5652c11bd 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -112,7 +112,7 @@ fn deserialize_compatible(fields: &[&Field]) -> TokenStream 
{
                     #(#pattern_items),*
                     _ => {
                         // skip bytes
-                        println!("no need to deserialize {:?}", 
_field.field_name.as_str());
+                        println!("no need to deserialize {:?}:{:?}", 
_field.field_name.as_str(), _field.field_type);
                         let _ = context
                         .get_fory()
                         .get_type_resolver()
diff --git a/rust/fory-derive/src/object/util.rs 
b/rust/fory-derive/src/object/util.rs
index ddae33b0f..8981ddaee 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use fory_core::types::TypeId;
 use proc_macro2::TokenStream;
 use quote::quote;
 use std::fmt;
@@ -94,9 +95,19 @@ pub(super) fn generic_tree_to_tokens(node: &TypeNode, 
have_context: bool) -> Tok
             fory
         }
     };
+    let get_type_id = if node.name == "Option" {
+        let option_type_id: i16 = TypeId::ForyOption.into();
+        quote! {
+            #option_type_id
+        }
+    } else {
+        quote! {
+            <#ty as fory_core::serializer::Serializer>::get_type_id(#param)
+        }
+    };
     quote! {
         fory_core::meta::FieldType::new(
-            <#ty as fory_core::serializer::Serializer>::get_type_id(#param),
+            #get_type_id,
             vec![#(#children_tokens),*] as Vec<fory_core::meta::FieldType>
         )
     }
diff --git a/rust/tests/tests/test_compatible.rs 
b/rust/tests/tests/test_compatible.rs
index 4e72f4735..f31e5d4a1 100644
--- a/rust/tests/tests/test_compatible.rs
+++ b/rust/tests/tests/test_compatible.rs
@@ -65,20 +65,27 @@ fn simple() {
 
 #[test]
 fn option() {
-    #[derive(Fory, Debug)]
+    #[derive(Fory, Debug, PartialEq)]
     struct Animal {
         f1: Option<String>,
         f2: Option<String>,
+        f3: Vec<Option<String>>,
+        // adjacent Options are not supported
+        // f4: Option<Option<String>>,
+        f5: Vec<Option<Vec<Option<String>>>>,
     }
-    let mut fory1 = Fory::default().mode(Compatible);
-    fory1.register::<Animal>(999);
+    let mut fory = Fory::default().mode(Compatible);
+    fory.register::<Animal>(999);
     let animal: Animal = Animal {
-        f1: Some(String::from("foo")),
+        f1: Some(String::from("f1")),
         f2: None,
+        f3: vec![Option::<String>::None, Some(String::from("f3"))],
+        // f4: Some(Some(String::from("f4"))),
+        f5: vec![Some(vec![Some(String::from("f1"))])],
     };
-    let _bin = fory1.serialize(&animal);
-    // todo
-    // let obj: crate::Animal2 = fory2.deserialize(&bin).unwrap();
+    let bin = fory.serialize(&animal);
+    let obj: Animal = fory.deserialize(&bin).unwrap();
+    assert_eq!(animal, obj);
 }
 
 // #[test]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to