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 3772f7b73 feat: ✨ support fory skip macro attributes(#2864) (#2865)
3772f7b73 is described below
commit 3772f7b73e752241cee148ff640116168f4e90ae
Author: kitty <[email protected]>
AuthorDate: Sun Nov 2 15:13:06 2025 +0800
feat: ✨ support fory skip macro attributes(#2864) (#2865)
## Why?
This PR adds a `#[fory(skip)]` macro attribute similar to
`#serde[(skip)]` to provide more flexible field skipping capabilities in
Apache Fory™. This enhancement allows developers to easily mark specific
fields to be skipped during serialization/deserialization or other
processing phases, improving code readability and maintainability.
## What does this PR do?
- Adds a new `#[fory(skip)]` macro attribute implementation
- The attribute functions similarly to `#[serde(skip)]` but is
specifically designed for Fory's internal use cases
- Provides consistent attribute parsing and handling within Fory's macro
system
- Maintains backward compatibility with existing code
## Related issues
- [[Rust] support fory macro attributes
](https://github.com/apache/fory/issues/2864)
## 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?
**No**, this PR only adds a new macro attribute that doesn't affect
existing public APIs or binary protocols. The `#[fory(skip)]` attribute
is opt-in and doesn't change the behavior of existing code.
## Benchmark
**No performance impact expected** as this change only adds attribute
parsing capability without modifying core processing logic. The macro
attribute is processed at compile time and doesn't introduce runtime
overhead.
---------
Co-authored-by: Shawn Yang <[email protected]>
---
rust/fory-derive/src/lib.rs | 2 +-
rust/fory-derive/src/object/derive_enum.rs | 31 ++-
rust/fory-derive/src/object/misc.rs | 6 +-
rust/fory-derive/src/object/read.rs | 251 ++++++++++-------
rust/fory-derive/src/object/util.rs | 45 ++-
rust/fory-derive/src/object/write.rs | 7 +-
rust/tests/tests/test_skip_fields.rs | 433 +++++++++++++++++++++++++++++
7 files changed, 654 insertions(+), 121 deletions(-)
diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs
index 0ef903e71..49901b8d6 100644
--- a/rust/fory-derive/src/lib.rs
+++ b/rust/fory-derive/src/lib.rs
@@ -198,7 +198,7 @@ mod util;
/// city: String,
/// }
/// ```
-#[proc_macro_derive(ForyObject, attributes(fory_debug))]
+#[proc_macro_derive(ForyObject, attributes(fory_debug, fory))]
pub fn proc_macro_derive_fory_object(input: proc_macro::TokenStream) ->
TokenStream {
let input = parse_macro_input!(input as DeriveInput);
diff --git a/rust/fory-derive/src/object/derive_enum.rs
b/rust/fory-derive/src/object/derive_enum.rs
index c7a96c422..93462e2ca 100644
--- a/rust/fory-derive/src/object/derive_enum.rs
+++ b/rust/fory-derive/src/object/derive_enum.rs
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+use super::util::{is_default_value_variant, is_skip_enum_variant};
use crate::object::read::gen_read_field;
use crate::object::write::gen_write_field;
@@ -51,13 +52,21 @@ pub fn gen_write(_data_enum: &DataEnum) -> TokenStream {
}
pub fn gen_write_data(data_enum: &DataEnum) -> TokenStream {
+ let default_variant_value = data_enum
+ .variants
+ .iter()
+ .position(is_default_value_variant)
+ .unwrap_or(0) as u32;
let xlang_variant_branches: Vec<TokenStream> = data_enum
.variants
.iter()
.enumerate()
.map(|(idx, v)| {
let ident = &v.ident;
- let tag_value = idx as u32;
+ let mut tag_value = idx as u32;
+ if is_skip_enum_variant(v) {
+ tag_value = default_variant_value;
+ }
match &v.fields {
Fields::Unit => {
@@ -91,7 +100,10 @@ pub fn gen_write_data(data_enum: &DataEnum) -> TokenStream {
.enumerate()
.map(|(idx, v)| {
let ident = &v.ident;
- let tag_value = idx as u32;
+ let mut tag_value = idx as u32;
+ if is_skip_enum_variant(v) {
+ tag_value = default_variant_value;
+ }
match &v.fields {
Fields::Unit => {
@@ -186,13 +198,21 @@ pub fn gen_read_with_type_info(_: &DataEnum) ->
TokenStream {
}
pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
+ let default_variant_value = data_enum
+ .variants
+ .iter()
+ .position(is_default_value_variant)
+ .unwrap_or(0) as u32;
let xlang_variant_branches: Vec<TokenStream> = data_enum
.variants
.iter()
.enumerate()
.map(|(idx, v)| {
let ident = &v.ident;
- let tag_value = idx as u32;
+ let mut tag_value = idx as u32;
+ if is_skip_enum_variant(v) {
+ tag_value = default_variant_value;
+ }
match &v.fields {
Fields::Unit => {
@@ -237,7 +257,10 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
.enumerate()
.map(|(idx, v)| {
let ident = &v.ident;
- let tag_value = idx as u32;
+ let mut tag_value = idx as u32;
+ if is_skip_enum_variant(v) {
+ tag_value = default_variant_value;
+ }
match &v.fields {
Fields::Unit => {
diff --git a/rust/fory-derive/src/object/misc.rs
b/rust/fory-derive/src/object/misc.rs
index f122198c8..1e35ebafa 100644
--- a/rust/fory-derive/src/object/misc.rs
+++ b/rust/fory-derive/src/object/misc.rs
@@ -21,8 +21,8 @@ use std::sync::atomic::{AtomicU32, Ordering};
use syn::Field;
use super::util::{
- classify_trait_object_field, generic_tree_to_tokens, get_sort_fields_ts,
parse_generic_tree,
- StructField,
+ classify_trait_object_field, generic_tree_to_tokens,
get_filtered_fields_iter,
+ get_sort_fields_ts, parse_generic_tree, StructField,
};
// Global type ID counter that auto-grows from 0 at macro processing time
@@ -72,7 +72,7 @@ pub fn gen_get_sorted_field_names(fields: &[&Field]) ->
TokenStream {
}
pub fn gen_field_fields_info(fields: &[&Field]) -> TokenStream {
- let field_infos = fields.iter().map(|field| {
+ let field_infos = get_filtered_fields_iter(fields).map(|field| {
let ty = &field.ty;
let name = format!("{}", field.ident.as_ref().expect("should be field
name"));
match classify_trait_object_field(ty) {
diff --git a/rust/fory-derive/src/object/read.rs
b/rust/fory-derive/src/object/read.rs
index 1bdb8eb11..fc8573173 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -22,7 +22,7 @@ use syn::{Field, Type};
use super::util::{
classify_trait_object_field, compute_struct_version_hash,
create_wrapper_types_arc,
create_wrapper_types_rc, extract_type_name, get_struct_name,
is_debug_enabled,
- is_primitive_type, should_skip_type_info_for_field, skip_ref_flag,
StructField,
+ is_primitive_type, is_skip_field, should_skip_type_info_for_field,
skip_ref_flag, StructField,
};
fn create_private_field_name(field: &Field) -> Ident {
@@ -103,6 +103,12 @@ fn assign_value(fields: &[&Field]) -> Vec<TokenStream> {
pub fn gen_read_field(field: &Field, private_ident: &Ident) -> TokenStream {
let ty = &field.ty;
+ if is_skip_field(field) {
+ return quote! {
+ let #private_ident = <#ty as
fory_core::ForyDefault>::fory_default();
+ };
+ }
+
let base = match classify_trait_object_field(ty) {
StructField::BoxDyn => {
quote! {
@@ -277,146 +283,175 @@ pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
fn gen_read_compatible_match_arm_body(field: &Field, var_name: &Ident) ->
TokenStream {
let ty = &field.ty;
let field_kind = classify_trait_object_field(ty);
+ let is_skip_flag = is_skip_field(field);
- let base = match field_kind {
- StructField::BoxDyn => {
- quote! {
- #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, true)?);
+ let base = if is_skip_flag {
+ match field_kind {
+ StructField::None => {
+ // Note: _base_ty is currently unused but kept for potential
future use
+ let _base_ty = match &ty {
+ Type::Path(type_path) =>
Some(&type_path.path.segments.first().unwrap().ident),
+ Type::Tuple(_) => None, // Tuples don't have a simple ident
+ _ => None, // Other types also don't have a
simple ident
+ };
+ let dec_by_option = need_declared_by_option(field);
+ if dec_by_option {
+ quote! {
+ #var_name = Some(<#ty as
fory_core::ForyDefault>::fory_default());
+ }
+ } else {
+ quote! {
+ #var_name = <#ty as
fory_core::ForyDefault>::fory_default();
+ }
+ }
}
- }
- StructField::RcDyn(trait_name) => {
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper = <#wrapper_ty as
fory_core::Serializer>::fory_read(context, true, true)?;
- #var_name = Some(std::rc::Rc::<dyn
#trait_ident>::from(wrapper));
+ _ => {
+ quote! {
+ #var_name = Some(<#ty as
fory_core::ForyDefault>::fory_default());
+ }
}
}
- StructField::ArcDyn(trait_name) => {
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper = <#wrapper_ty as
fory_core::Serializer>::fory_read(context, true, true)?;
- #var_name = Some(std::sync::Arc::<dyn
#trait_ident>::from(wrapper));
+ } else {
+ match field_kind {
+ StructField::BoxDyn => {
+ quote! {
+ #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, true)?);
+ }
}
- }
- StructField::VecRc(trait_name) => {
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_vec = <Vec<#wrapper_ty> as
fory_core::Serializer>::fory_read(context, true, false)?;
- #var_name = Some(wrapper_vec.into_iter()
- .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w))
- .collect());
+ StructField::RcDyn(trait_name) => {
+ let types = create_wrapper_types_rc(&trait_name);
+ let wrapper_ty = types.wrapper_ty;
+ let trait_ident = types.trait_ident;
+ quote! {
+ let wrapper = <#wrapper_ty as
fory_core::Serializer>::fory_read(context, true, true)?;
+ #var_name = Some(std::rc::Rc::<dyn
#trait_ident>::from(wrapper));
+ }
}
- }
- StructField::VecArc(trait_name) => {
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_vec = <Vec<#wrapper_ty> as
fory_core::Serializer>::fory_read(context, true, false)?;
- #var_name = Some(wrapper_vec.into_iter()
- .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w))
- .collect());
+ StructField::ArcDyn(trait_name) => {
+ let types = create_wrapper_types_arc(&trait_name);
+ let wrapper_ty = types.wrapper_ty;
+ let trait_ident = types.trait_ident;
+ quote! {
+ let wrapper = <#wrapper_ty as
fory_core::Serializer>::fory_read(context, true, true)?;
+ #var_name = Some(std::sync::Arc::<dyn
#trait_ident>::from(wrapper));
+ }
}
- }
- StructField::HashMapRc(key_ty, trait_name) => {
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_map = <std::collections::HashMap<#key_ty,
#wrapper_ty> as fory_core::Serializer>::fory_read(context, true, false)?;
- #var_name = Some(wrapper_map.into_iter()
- .map(|(k, v)| (k, std::rc::Rc::<dyn
#trait_ident>::from(v)))
- .collect());
+ StructField::VecRc(trait_name) => {
+ let types = create_wrapper_types_rc(&trait_name);
+ let wrapper_ty = types.wrapper_ty;
+ let trait_ident = types.trait_ident;
+ quote! {
+ let wrapper_vec = <Vec<#wrapper_ty> as
fory_core::Serializer>::fory_read(context, true, false)?;
+ #var_name = Some(wrapper_vec.into_iter()
+ .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w))
+ .collect());
+ }
}
- }
- StructField::HashMapArc(key_ty, trait_name) => {
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_map = <std::collections::HashMap<#key_ty,
#wrapper_ty> as fory_core::Serializer>::fory_read(context, true, false)?;
- #var_name = Some(wrapper_map.into_iter()
- .map(|(k, v)| (k, std::sync::Arc::<dyn
#trait_ident>::from(v)))
- .collect());
+ StructField::VecArc(trait_name) => {
+ let types = create_wrapper_types_arc(&trait_name);
+ let wrapper_ty = types.wrapper_ty;
+ let trait_ident = types.trait_ident;
+ quote! {
+ let wrapper_vec = <Vec<#wrapper_ty> as
fory_core::Serializer>::fory_read(context, true, false)?;
+ #var_name = Some(wrapper_vec.into_iter()
+ .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w))
+ .collect());
+ }
}
- }
- StructField::ContainsTraitObject => {
- quote! {
- #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, true)?);
+ StructField::HashMapRc(key_ty, trait_name) => {
+ let types = create_wrapper_types_rc(&trait_name);
+ let wrapper_ty = types.wrapper_ty;
+ let trait_ident = types.trait_ident;
+ quote! {
+ let wrapper_map = <std::collections::HashMap<#key_ty,
#wrapper_ty> as fory_core::Serializer>::fory_read(context, true, false)?;
+ #var_name = Some(wrapper_map.into_iter()
+ .map(|(k, v)| (k, std::rc::Rc::<dyn
#trait_ident>::from(v)))
+ .collect());
+ }
}
- }
- StructField::Forward => {
- quote! {
- #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, true)?);
+ StructField::HashMapArc(key_ty, trait_name) => {
+ let types = create_wrapper_types_arc(&trait_name);
+ let wrapper_ty = types.wrapper_ty;
+ let trait_ident = types.trait_ident;
+ quote! {
+ let wrapper_map = <std::collections::HashMap<#key_ty,
#wrapper_ty> as fory_core::Serializer>::fory_read(context, true, false)?;
+ #var_name = Some(wrapper_map.into_iter()
+ .map(|(k, v)| (k, std::sync::Arc::<dyn
#trait_ident>::from(v)))
+ .collect());
+ }
}
- }
- StructField::None => {
- // Note: _base_ty is currently unused but kept for potential
future use
- let _base_ty = match &ty {
- Type::Path(type_path) =>
Some(&type_path.path.segments.first().unwrap().ident),
- Type::Tuple(_) => None, // Tuples don't have a simple ident
- _ => None, // Other types also don't have a
simple ident
- };
- let skip_type_info = should_skip_type_info_for_field(ty);
- let dec_by_option = need_declared_by_option(field);
- if skip_type_info {
- if dec_by_option {
+ StructField::ContainsTraitObject => {
+ quote! {
+ #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, true)?);
+ }
+ }
+ StructField::Forward => {
+ quote! {
+ #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, true)?);
+ }
+ }
+ StructField::None => {
+ // Note: _base_ty is currently unused but kept for potential
future use
+ let _base_ty = match &ty {
+ Type::Path(type_path) =>
Some(&type_path.path.segments.first().unwrap().ident),
+ Type::Tuple(_) => None, // Tuples don't have a simple ident
+ _ => None, // Other types also don't have a
simple ident
+ };
+ let skip_type_info = should_skip_type_info_for_field(ty);
+ let dec_by_option = need_declared_by_option(field);
+ if skip_type_info {
+ if dec_by_option {
+ quote! {
+ let read_ref_flag =
fory_core::serializer::util::field_need_write_ref_into(
+ _field.field_type.type_id,
+ _field.field_type.nullable,
+ );
+ if read_ref_flag {
+ #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, false)?);
+ } else {
+ #var_name = Some(<#ty as
fory_core::Serializer>::fory_read_data(context)?);
+ }
+ }
+ } else {
+ quote! {
+ let read_ref_flag =
fory_core::serializer::util::field_need_write_ref_into(
+ _field.field_type.type_id,
+ _field.field_type.nullable,
+ );
+ if read_ref_flag {
+ #var_name = <#ty as
fory_core::Serializer>::fory_read(context, true, false)?;
+ } else {
+ #var_name = <#ty as
fory_core::Serializer>::fory_read_data(context)?;
+ }
+ }
+ }
+ } else if dec_by_option {
quote! {
+ let read_type_info =
fory_core::serializer::util::field_need_read_type_info(_field.field_type.type_id);
let read_ref_flag =
fory_core::serializer::util::field_need_write_ref_into(
_field.field_type.type_id,
_field.field_type.nullable,
);
if read_ref_flag {
- #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, false)?);
+ #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, read_type_info)?);
} else {
#var_name = Some(<#ty as
fory_core::Serializer>::fory_read_data(context)?);
}
}
} else {
quote! {
+ let read_type_info =
fory_core::serializer::util::field_need_read_type_info(_field.field_type.type_id);
let read_ref_flag =
fory_core::serializer::util::field_need_write_ref_into(
_field.field_type.type_id,
_field.field_type.nullable,
);
if read_ref_flag {
- #var_name = <#ty as
fory_core::Serializer>::fory_read(context, true, false)?;
+ #var_name = <#ty as
fory_core::Serializer>::fory_read(context, true, read_type_info)?;
} else {
#var_name = <#ty as
fory_core::Serializer>::fory_read_data(context)?;
}
}
}
- } else if dec_by_option {
- quote! {
- let read_type_info =
fory_core::serializer::util::field_need_read_type_info(_field.field_type.type_id);
- let read_ref_flag =
fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- if read_ref_flag {
- #var_name = Some(<#ty as
fory_core::Serializer>::fory_read(context, true, read_type_info)?);
- } else {
- #var_name = Some(<#ty as
fory_core::Serializer>::fory_read_data(context)?);
- }
- }
- } else {
- quote! {
- let read_type_info =
fory_core::serializer::util::field_need_read_type_info(_field.field_type.type_id);
- let read_ref_flag =
fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- if read_ref_flag {
- #var_name = <#ty as
fory_core::Serializer>::fory_read(context, true, read_type_info)?;
- } else {
- #var_name = <#ty as
fory_core::Serializer>::fory_read_data(context)?;
- }
- }
}
}
};
diff --git a/rust/fory-derive/src/object/util.rs
b/rust/fory-derive/src/object/util.rs
index 50def2ca0..92e184ced 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -767,8 +767,14 @@ pub(crate) fn get_sorted_field_names(fields: &[&Field]) ->
Vec<String> {
all_fields.into_iter().map(|(name, _, _)| name).collect()
}
+pub(crate) fn get_filtered_fields_iter<'a>(
+ fields: &'a [&'a Field],
+) -> impl Iterator<Item = &'a Field> {
+ fields.iter().filter(|field| !is_skip_field(field)).copied()
+}
pub(super) fn get_sort_fields_ts(fields: &[&Field]) -> TokenStream {
- let sorted_names = get_sorted_field_names(fields);
+ let filterd_fields: Vec<&Field> =
get_filtered_fields_iter(fields).collect();
+ let sorted_names = get_sorted_field_names(&filterd_fields);
let names = sorted_names.iter().map(|name| {
quote! { #name }
});
@@ -898,6 +904,43 @@ pub(crate) fn should_skip_type_info_for_field(ty: &Type)
-> bool {
true
}
+pub(crate) fn is_skip_field(field: &syn::Field) -> bool {
+ field.attrs.iter().any(|attr| {
+ attr.path().is_ident("fory") && {
+ let mut skip = false;
+ let _ = attr.parse_nested_meta(|meta| {
+ if meta.path.is_ident("skip") {
+ skip = true;
+ }
+ Ok(())
+ });
+ skip
+ }
+ })
+}
+
+pub(crate) fn is_skip_enum_variant(variant: &syn::Variant) -> bool {
+ variant.attrs.iter().any(|attr| {
+ attr.path().is_ident("fory") && {
+ let mut skip = false;
+ let _ = attr.parse_nested_meta(|meta| {
+ if meta.path.is_ident("skip") {
+ skip = true;
+ }
+ Ok(())
+ });
+ skip
+ }
+ })
+}
+
+pub(crate) fn is_default_value_variant(variant: &syn::Variant) -> bool {
+ variant
+ .attrs
+ .iter()
+ .any(|attr| attr.path().is_ident("default"))
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/rust/fory-derive/src/object/write.rs
b/rust/fory-derive/src/object/write.rs
index fba08ced4..4c1b505bc 100644
--- a/rust/fory-derive/src/object/write.rs
+++ b/rust/fory-derive/src/object/write.rs
@@ -17,8 +17,8 @@
use super::util::{
classify_trait_object_field, compute_struct_version_hash,
create_wrapper_types_arc,
- create_wrapper_types_rc, get_struct_name, get_type_id_by_type_ast,
is_debug_enabled,
- should_skip_type_info_for_field, skip_ref_flag, StructField,
+ create_wrapper_types_rc, get_filtered_fields_iter, get_struct_name,
get_type_id_by_type_ast,
+ is_debug_enabled, should_skip_type_info_for_field, skip_ref_flag,
StructField,
};
use fory_core::types::TypeId;
use proc_macro2::{Ident, TokenStream};
@@ -247,8 +247,7 @@ pub fn gen_write_field(field: &Field, ident: &Ident,
use_self: bool) -> TokenStr
}
pub fn gen_write_data(fields: &[&Field]) -> TokenStream {
- let write_fields_ts: Vec<_> = fields
- .iter()
+ let write_fields_ts: Vec<_> = get_filtered_fields_iter(fields)
.map(|field| {
let ident = field.ident.as_ref().unwrap();
gen_write_field(field, ident, true)
diff --git a/rust/tests/tests/test_skip_fields.rs
b/rust/tests/tests/test_skip_fields.rs
new file mode 100644
index 000000000..00e8e4949
--- /dev/null
+++ b/rust/tests/tests/test_skip_fields.rs
@@ -0,0 +1,433 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use fory_core::{Fory, ForyDefault, Reader};
+use fory_derive::ForyObject;
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct TestSkipFields {
+ serialized_field: i32,
+ #[fory(skip)]
+ skipped_field: String,
+ another_serialized: f64,
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct NestedStruct {
+ value: i32,
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct TestNestedSkip {
+ normal_field: i32,
+ nested: NestedStruct,
+ #[fory(skip)]
+ skipped_nested: NestedStruct,
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct MultipleSkipFields {
+ field1: i32,
+ #[fory(skip)]
+ skipped1: String,
+ field2: f64,
+ #[fory(skip)]
+ skipped2: bool,
+ field3: f32,
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct AllFieldsSkipped {
+ #[fory(skip)]
+ skipped1: String,
+ #[fory(skip)]
+ skipped2: i32,
+ #[fory(skip)]
+ skipped3: f64,
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+struct ComplexNestedSkip {
+ normal_field: i32,
+ #[fory(skip)]
+ skipped_field: String,
+ nested: TestSkipFields,
+ #[fory(skip)]
+ skipped_nested: TestSkipFields,
+}
+
+#[derive(ForyObject, Debug, PartialEq)]
+enum TestEnumSkip {
+ Pending,
+ // #[default]
+ Active,
+ Inactive,
+ #[fory(skip)]
+ Deleted,
+}
+
+#[test]
+fn test_basic_skip_functionality() {
+ let mut fory = Fory::default();
+ fory.register::<TestSkipFields>(1).unwrap();
+
+ let original = TestSkipFields {
+ serialized_field: 42,
+ skipped_field: "this should be skipped".to_string(),
+ another_serialized: 2.142,
+ };
+
+ let bytes = fory.serialize(&original).unwrap();
+ let decoded: TestSkipFields = fory.deserialize(&bytes).unwrap();
+ assert_eq!(original.serialized_field, decoded.serialized_field);
+ assert_eq!(original.another_serialized, decoded.another_serialized);
+ assert_eq!(decoded.skipped_field, String::default());
+
+ let mut buf: Vec<u8> = vec![];
+ fory.serialize_to(&original, &mut buf).unwrap();
+ let mut reader = Reader::new(&buf);
+ let decoded: TestSkipFields = fory.deserialize_from(&mut reader).unwrap();
+ assert_eq!(original.serialized_field, decoded.serialized_field);
+ assert_eq!(original.another_serialized, decoded.another_serialized);
+ assert_eq!(decoded.skipped_field, String::default());
+}
+
+#[test]
+fn test_nested_skip_functionality() {
+ let mut fory = Fory::default();
+ fory.register::<TestNestedSkip>(2).unwrap();
+ fory.register::<NestedStruct>(3).unwrap();
+
+ let original = TestNestedSkip {
+ normal_field: 100,
+ nested: NestedStruct { value: 200 },
+ skipped_nested: NestedStruct { value: 300 },
+ };
+
+ let bytes = fory.serialize(&original).unwrap();
+ let decoded: TestNestedSkip = fory.deserialize(&bytes).unwrap();
+
+ assert_eq!(original.normal_field, decoded.normal_field);
+ assert_eq!(original.nested, decoded.nested);
+ assert_eq!(decoded.skipped_nested, NestedStruct::default());
+}
+
+#[test]
+fn test_multiple_skip_fields() {
+ let mut fory = Fory::default();
+ fory.register::<MultipleSkipFields>(3).unwrap();
+
+ let original = MultipleSkipFields {
+ field1: 42,
+ skipped1: "skipped string".to_string(),
+ field2: 2.71,
+ skipped2: true,
+ field3: 255.9,
+ };
+
+ let bytes = fory.serialize(&original).unwrap();
+ let decoded: MultipleSkipFields = fory.deserialize(&bytes).unwrap();
+
+ assert_eq!(original.field1, decoded.field1);
+ assert_eq!(original.field2, decoded.field2);
+ assert_eq!(original.field3, decoded.field3);
+ assert_eq!(decoded.skipped1, String::default());
+ assert_eq!(decoded.skipped2, bool::default());
+}
+
+#[test]
+fn test_all_fields_skipped() {
+ let mut fory = Fory::default();
+ fory.register::<AllFieldsSkipped>(4).unwrap();
+
+ let original = AllFieldsSkipped {
+ skipped1: "test1".to_string(),
+ skipped2: 42,
+ skipped3: 2.142,
+ };
+
+ let bytes = fory.serialize(&original).unwrap();
+ let decoded: AllFieldsSkipped = fory.deserialize(&bytes).unwrap();
+
+ assert_eq!(decoded.skipped1, String::default());
+ assert_eq!(decoded.skipped2, i32::default());
+ assert_eq!(decoded.skipped3, f64::default());
+}
+
+#[test]
+fn test_complex_nested_skip() {
+ let mut fory = Fory::default();
+ fory.register::<ComplexNestedSkip>(5).unwrap();
+ fory.register::<TestSkipFields>(6).unwrap();
+
+ let original = ComplexNestedSkip {
+ normal_field: 1,
+ skipped_field: "should be skipped".to_string(),
+ nested: TestSkipFields {
+ serialized_field: 2,
+ skipped_field: "nested skipped".to_string(),
+ another_serialized: 1.41,
+ },
+ skipped_nested: TestSkipFields {
+ serialized_field: 3,
+ skipped_field: "completely skipped".to_string(),
+ another_serialized: 2.71,
+ },
+ };
+
+ let bytes = fory.serialize(&original).unwrap();
+ let decoded: ComplexNestedSkip = fory.deserialize(&bytes).unwrap();
+
+ assert_eq!(original.normal_field, decoded.normal_field);
+ assert_eq!(
+ original.nested.serialized_field,
+ decoded.nested.serialized_field
+ );
+ assert_eq!(decoded.nested.skipped_field, String::default());
+ assert_eq!(decoded.skipped_field, String::default());
+ assert_eq!(decoded.skipped_nested, TestSkipFields::default());
+}
+
+#[test]
+fn test_enum_skip() {
+ let mut fory = Fory::default();
+ fory.register::<TestEnumSkip>(6).unwrap();
+
+ let original_v1 = TestEnumSkip::Pending;
+
+ let bytes = fory.serialize(&original_v1).unwrap();
+ let decoded: TestEnumSkip = fory.deserialize(&bytes).unwrap();
+ assert_eq!(original_v1, decoded);
+
+ let original_skip = TestEnumSkip::Deleted;
+ let bytes = fory.serialize(&original_skip).unwrap();
+ let decoded: TestEnumSkip = fory.deserialize(&bytes).unwrap();
+ assert_eq!(decoded, TestEnumSkip::default());
+}
+
+#[test]
+fn test_skip_serialization_size() {
+ let mut fory = Fory::default();
+ fory.register::<TestSkipFields>(10).unwrap();
+
+ let with_skip = TestSkipFields {
+ serialized_field: 42,
+ skipped_field: "this is a long string that should be
skipped".to_string(),
+ another_serialized: 2.142,
+ };
+ #[derive(ForyObject, Debug, PartialEq)]
+ struct TestNoSkip {
+ serialized_field: i32,
+ skipped_field: String,
+ another_serialized: f64,
+ }
+
+ fory.register::<TestNoSkip>(11).unwrap();
+
+ let without_skip = TestNoSkip {
+ serialized_field: 42,
+ skipped_field: "this is a long string that should be
skipped".to_string(),
+ another_serialized: 2.142,
+ };
+
+ let bytes_with_skip = fory.serialize(&with_skip).unwrap();
+ let bytes_without_skip = fory.serialize(&without_skip).unwrap();
+
+ assert!(
+ bytes_with_skip.len() < bytes_without_skip.len(),
+ "Skipped version should be smaller: {} < {}",
+ bytes_with_skip.len(),
+ bytes_without_skip.len()
+ );
+}
+
+#[test]
+fn test_skip_with_different_types() {
+ #[derive(ForyObject, Debug, PartialEq)]
+ struct MultiTypeSkip {
+ field1: i32,
+ #[fory(skip)]
+ skipped_string: String,
+ field2: f64,
+ #[fory(skip)]
+ skipped_bool: bool,
+ field3: i8,
+ #[fory(skip)]
+ skipped_vec: Vec<i32>,
+ field4: i64,
+ }
+
+ let mut fory = Fory::default();
+ fory.register::<MultiTypeSkip>(12).unwrap();
+
+ let original = MultiTypeSkip {
+ field1: 1,
+ skipped_string: "test".to_string(),
+ field2: 2.0,
+ skipped_bool: true,
+ field3: 3,
+ skipped_vec: vec![1, 2, 3],
+ field4: 4,
+ };
+ let bytes = fory.serialize(&original).unwrap();
+ let decoded: MultiTypeSkip = fory.deserialize(&bytes).unwrap();
+
+ assert_eq!(original.field1, decoded.field1);
+ assert_eq!(original.field2, decoded.field2);
+ assert_eq!(original.field3, decoded.field3);
+ assert_eq!(original.field4, decoded.field4);
+
+ assert_eq!(decoded.skipped_string, String::default());
+ assert_eq!(decoded.skipped_bool, bool::default());
+ assert_eq!(decoded.skipped_vec, Vec::<i32>::default());
+}
+
+#[test]
+fn test_trait_object_serialization() {
+ use fory_core::ForyDefault;
+ use fory_core::Serializer;
+ use fory_core::{register_trait_type, Fory};
+ use fory_derive::ForyObject;
+ use std::collections::HashMap;
+ use std::rc::Rc;
+ use std::sync::Arc;
+
+ trait Animal: Serializer {
+ fn speak(&self) -> String;
+ fn name(&self) -> &str;
+ }
+
+ #[derive(ForyObject, Debug)]
+ struct Dog {
+ name: String,
+ breed: String,
+ }
+
+ impl Animal for Dog {
+ fn speak(&self) -> String {
+ "Woof!".to_string()
+ }
+ fn name(&self) -> &str {
+ &self.name
+ }
+ }
+
+ #[derive(ForyObject, Debug)]
+ struct Cat {
+ name: String,
+ color: String,
+ }
+
+ impl Animal for Cat {
+ fn speak(&self) -> String {
+ "Meow!".to_string()
+ }
+ fn name(&self) -> &str {
+ &self.name
+ }
+ }
+
+ register_trait_type!(Animal, Dog, Cat);
+
+ #[derive(ForyObject)]
+ struct Zoo {
+ star_animal: Box<dyn Animal>,
+ }
+
+ #[derive(ForyObject)]
+ struct ZooWithSkip {
+ regular_animal: Box<dyn Animal>,
+ #[fory(skip)]
+ skipped_animal: Box<dyn Animal>,
+ }
+
+ let mut fory = Fory::default().compatible(true);
+ fory.register::<Dog>(100).unwrap();
+ fory.register::<Cat>(101).unwrap();
+ fory.register::<Zoo>(102).unwrap();
+ fory.register::<ZooWithSkip>(103).unwrap();
+
+ let zoo_with_skip = ZooWithSkip {
+ regular_animal: Box::new(Dog {
+ name: "Speedy".to_string(),
+ breed: "Greyhound".to_string(),
+ }),
+ skipped_animal: Box::new(Cat {
+ name: "Felix".to_string(),
+ color: "Black".to_string(),
+ }),
+ };
+
+ let bytes_skip = fory.serialize(&zoo_with_skip).unwrap();
+
+ let decoded_skip: ZooWithSkip = fory.deserialize(&bytes_skip).unwrap();
+
+ assert_eq!(decoded_skip.regular_animal.name(), "Speedy");
+ assert_eq!(decoded_skip.regular_animal.speak(), "Woof!");
+
+ assert_eq!(decoded_skip.skipped_animal.name(), "".to_string());
+ assert_eq!(decoded_skip.skipped_animal.speak(), "Woof!".to_string());
+
+ #[derive(ForyObject)]
+ struct ComplexSkipExample {
+ #[fory(skip)]
+ boxed_dyn: Box<dyn Animal>,
+ #[fory(skip)]
+ animals_arc: Vec<Arc<dyn Animal>>,
+ #[fory(skip)]
+ registry: HashMap<String, Arc<dyn Animal>>,
+ #[fory(skip)]
+ animals_rc: Vec<Rc<dyn Animal>>,
+ normal_field: String,
+ }
+
+ let mut fory = Fory::default().compatible(true);
+ fory.register::<ComplexSkipExample>(106).unwrap();
+
+ let complex = ComplexSkipExample {
+ boxed_dyn: Box::new(Dog {
+ name: "BoxTest".to_string(),
+ breed: "BoxTestBreed".to_string(),
+ }) as Box<dyn Animal>,
+ animals_rc: vec![Rc::new(Cat {
+ name: "RcTest".to_string(),
+ color: "RcTestColor".to_string(),
+ }) as Rc<dyn Animal>],
+ animals_arc: vec![Arc::new(Cat {
+ name: "RcTest".to_string(),
+ color: "RcTestColor".to_string(),
+ }) as Arc<dyn Animal>],
+ registry: HashMap::from_iter([(
+ "arc_map".to_string(),
+ Arc::new(Dog {
+ name: "ArcTest".to_string(),
+ breed: "ArcTestBreed".to_string(),
+ }) as Arc<dyn Animal>,
+ )]),
+ normal_field: "test_value".to_string(),
+ };
+
+ let complex_bytes = fory.serialize(&complex).unwrap();
+ let decoded_complex: ComplexSkipExample =
fory.deserialize(&complex_bytes).unwrap();
+
+ assert_eq!(decoded_complex.normal_field, "test_value");
+
+ assert_eq!(decoded_complex.boxed_dyn.name(), "".to_string());
+ assert!(decoded_complex.animals_rc.is_empty());
+ assert!(decoded_complex.animals_arc.is_empty());
+ assert!(decoded_complex.registry.is_empty());
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]