This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to tag v0.13.2-rc1 in repository https://gitbox.apache.org/repos/asf/fory.git
commit 8fb2eb6681e73236398e1c8ad748ce27ca899551 Author: Shawn Yang <[email protected]> AuthorDate: Mon Nov 3 23:38:34 2025 +0800 refactor(rust): merge fory_debug into fory macro attr (#2883) ## Why? <!-- Describe the purpose of this PR. --> ## What does this PR do? merge fory_debug into fory macro attr ## Related issues <!-- Is there any related issue? If this PR closes them you say say fix/closes: - #xxxx0 - #xxxx1 - Fixes #xxxx2 --> ## Does this PR introduce any user-facing change? <!-- If any user-facing interface changes, please [open an issue](https://github.com/apache/fory/issues/new/choose) describing the need to do so and update the document if necessary. Delete section if not applicable. --> - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark <!-- When the PR has an impact on performance (if you don't know whether the PR will have an impact on performance, you can submit the PR first, and if it will have impact on performance, the code reviewer will explain it), be sure to attach a benchmark data here. Delete section if not applicable. --> --- AGENTS.md | 3 +- rust/README.md | 2 +- rust/fory-derive/src/lib.rs | 52 ++++++++++++++++++++++--- rust/fory/src/lib.rs | 7 ++-- rust/tests/tests/compatible/test_container.rs | 2 +- rust/tests/tests/compatible/test_struct.rs | 20 +++++----- rust/tests/tests/compatible/test_struct_enum.rs | 20 +++++----- rust/tests/tests/test_cross_language.rs | 2 +- rust/tests/tests/test_debug.rs | 2 +- rust/tests/tests/test_max_dyn_depth.rs | 2 +- rust/tests/tests/test_one_struct.rs | 2 +- 11 files changed, 78 insertions(+), 36 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d1468a686..e3af36933 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -124,6 +124,7 @@ go generate ./... - All cargo commands must be executed within the `rust` directory. - All changes to `rust` must pass the clippy check and tests. - You must set `RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1` when debuging rust tests to get backtrace. +- You must add `-- --nocapture` to cargo test command when debuging tests. - You must not set `FORY_PANIC_ON_ERROR=1` when runing all rust tests to check whether all tests pass, some tests will check Error content, which will fail if error just panic. ```bash @@ -146,7 +147,7 @@ cargo test -p tests --test $test_file $test_method cargo test --test mod $dir$::$test_file::$test_method # debug specific test under subdirectory and get backtrace -RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 ENABLE_FORY_DEBUG_OUTPUT=1 cargo test --test mod $dir$::$test_file::$test_method +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 ENABLE_FORY_DEBUG_OUTPUT=1 cargo test --test mod $dir$::$test_file::$test_method -- --nocapture # inspect generated code by fory derive macro cargo expand --test mod $mod$::$file$ > expanded.rs diff --git a/rust/README.md b/rust/README.md index dbdd29da1..bbfa27c55 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1049,7 +1049,7 @@ Note: Static data types (non-dynamic types) are secure by nature and not subject - **Type registry errors**: An error like `TypeId ... not found in type_info registry` means the type was never registered with the current `Fory` instance. Confirm that every serializable struct or trait implementation calls `fory.register::<T>(type_id)` before serialization and that the same IDs are reused on the deserialize side. - **Quick error lookup**: Prefer the static constructors on `fory_core::error::Error` (`Error::type_mismatch`, `Error::invalid_data`, `Error::unknown`, etc.) rather than instantiating variants manually. This keeps diagnostics consistent and makes opt-in panics work. - **Panic on error for backtraces**: Toggle `FORY_PANIC_ON_ERROR=1` (or `true`) alongside `RUST_BACKTRACE=1` when running tests or binaries to panic at the exact site an error is constructed. Reset the variable afterwards to avoid aborting user-facing code paths. -- **Struct field tracing**: Add the `#[fory_debug]` attribute alongside `#[derive(ForyObject)]` to tell the macro to emit hook invocations for that type. Once compiled with debug hooks, call `set_before_write_field_func`, `set_after_write_field_func`, `set_before_read_field_func`, or `set_after_read_field_func` (from `fory-core/src/serializer/struct_.rs`) to plug in custom callbacks, and use `reset_struct_debug_hooks()` when you want the defaults back. +- **Struct field tracing**: Add the `#[fory(debug)]` attribute (or `#[fory(debug = true)]`) alongside `#[derive(ForyObject)]` to tell the macro to emit hook invocations for that type. Once compiled with debug hooks, call `set_before_write_field_func`, `set_after_write_field_func`, `set_before_read_field_func`, or `set_after_read_field_func` (from `fory-core/src/serializer/struct_.rs`) to plug in custom callbacks, and use `reset_struct_debug_hooks()` when you want the defaults back. - **Lightweight logging**: Without custom hooks, enable `ENABLE_FORY_DEBUG_OUTPUT=1` to print field-level read/write events emitted by the default hook functions. This is especially useful when investigating alignment or cursor mismatches. - **Test-time hygiene**: Some integration tests expect `FORY_PANIC_ON_ERROR` to remain unset. Export it only for focused debugging sessions, and prefer `cargo test --features tests -p tests --test <case>` when isolating failing scenarios. diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs index 49901b8d6..8ecb4cdbc 100644 --- a/rust/fory-derive/src/lib.rs +++ b/rust/fory-derive/src/lib.rs @@ -106,6 +106,14 @@ //! - Field accessor methods that return references to the underlying data //! - Efficient serialization without object allocation //! +//! ## Attributes +//! +//! - **`#[fory(debug)]` / `#[fory(debug = true)]`**: Enables per-field debug instrumentation +//! for the annotated struct, allowing you to install custom hooks via +//! `fory_core::serializer::struct_`. +//! - **`#[fory(skip)]`**: Marks an individual field (or enum variant) to be ignored by the +//! generated serializer, retaining compatibility with previous releases. +//! //! ## Field Types //! //! Both macros support a wide range of field types: @@ -168,7 +176,7 @@ use fory_row::derive_row; use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; +use syn::{parse_macro_input, spanned::Spanned, Attribute, DeriveInput, LitBool}; mod fory_row; mod object; @@ -198,16 +206,16 @@ mod util; /// city: String, /// } /// ``` -#[proc_macro_derive(ForyObject, attributes(fory_debug, fory))] +#[proc_macro_derive(ForyObject, attributes(fory))] pub fn proc_macro_derive_fory_object(input: proc_macro::TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); // Check if this is being applied to a trait (which is not possible with derive macros) // Derive macros can only be applied to structs, enums, and unions - let debug_enabled = input - .attrs - .iter() - .any(|attr| attr.path().is_ident("fory_debug")); + let debug_enabled = match parse_debug_flag(&input.attrs) { + Ok(flag) => flag, + Err(err) => return err.into_compile_error().into(), + }; object::derive_serializer(&input, debug_enabled) } @@ -236,3 +244,35 @@ pub fn proc_macro_derive_fory_row(input: proc_macro::TokenStream) -> TokenStream let input = parse_macro_input!(input as DeriveInput); derive_row(&input) } + +fn parse_debug_flag(attrs: &[Attribute]) -> syn::Result<bool> { + let mut debug_flag: Option<bool> = None; + + for attr in attrs { + if attr.path().is_ident("fory") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("debug") { + let value = if meta.input.is_empty() { + true + } else { + let lit: LitBool = meta.value()?.parse()?; + lit.value + }; + debug_flag = match debug_flag { + Some(existing) if existing != value => { + return Err(syn::Error::new( + meta.path.span(), + "conflicting `debug` attribute values", + )); + } + Some(_) => debug_flag, + None => Some(value), + }; + } + Ok(()) + })?; + } + } + + Ok(debug_flag.unwrap_or(false)) +} diff --git a/rust/fory/src/lib.rs b/rust/fory/src/lib.rs index b16fc5fc6..a4e85f31d 100644 --- a/rust/fory/src/lib.rs +++ b/rust/fory/src/lib.rs @@ -1179,9 +1179,10 @@ //! - **Panic on error for backtraces**: Set `FORY_PANIC_ON_ERROR=1` (or `true`) together with //! `RUST_BACKTRACE=1` while running tests or binaries to panic exactly where an error is //! constructed. Unset the variable afterwards so production paths keep returning `Result`. -//! - **Struct field tracing**: Add the `#[fory_debug]` attribute next to `#[derive(ForyObject)]` -//! when you need per-field instrumentation. Once compiled with debug hooks, call -//! `set_before_write_field_func`, `set_after_write_field_func`, `set_before_read_field_func`, or +//! - **Struct field tracing**: Add the `#[fory(debug)]` attribute (or `#[fory(debug = true)]`) +//! next to `#[derive(ForyObject)]` when you need per-field instrumentation. Once compiled with +//! debug hooks, call `set_before_write_field_func`, `set_after_write_field_func`, +//! `set_before_read_field_func`, or //! `set_after_read_field_func` from `fory_core::serializer::struct_` to install custom //! callbacks, and use `reset_struct_debug_hooks()` to restore defaults. //! - **Lightweight logging**: If custom callbacks are unnecessary, enable diff --git a/rust/tests/tests/compatible/test_container.rs b/rust/tests/tests/compatible/test_container.rs index 63b6d41bb..2076574ef 100644 --- a/rust/tests/tests/compatible/test_container.rs +++ b/rust/tests/tests/compatible/test_container.rs @@ -21,7 +21,7 @@ use fory_core::fory::Fory; use fory_derive::ForyObject; #[derive(ForyObject, PartialEq, Eq, Hash, Debug)] -#[fory_debug] +#[fory(debug)] struct Item { id: i32, } diff --git a/rust/tests/tests/compatible/test_struct.rs b/rust/tests/tests/compatible/test_struct.rs index cf41c2f71..62f3f5b59 100644 --- a/rust/tests/tests/compatible/test_struct.rs +++ b/rust/tests/tests/compatible/test_struct.rs @@ -142,7 +142,7 @@ fn nonexistent_struct() { #[test] fn option() { #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Animal { f1: Option<String>, f2: Option<String>, @@ -286,7 +286,7 @@ fn nullable_container() { #[test] fn inner_nullable() { #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Item1 { f1: Vec<Option<String>>, f2: HashSet<Option<i8>>, @@ -295,7 +295,7 @@ fn inner_nullable() { } #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Item2 { f1: Vec<String>, f2: HashSet<i8>, @@ -325,7 +325,7 @@ fn inner_nullable() { #[test] fn nullable_struct() { #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] pub struct Item { name: String, data: Vec<Option<String>>, @@ -333,7 +333,7 @@ fn nullable_struct() { } #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Person1 { f1: Item, f2: Option<Item>, @@ -342,7 +342,7 @@ fn nullable_struct() { } #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Person2 { f1: Option<Item>, f2: Item, @@ -397,7 +397,7 @@ fn enum_without_payload() { Blue, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Person1 { f1: Color1, f2: Color1, @@ -410,7 +410,7 @@ fn enum_without_payload() { last: i8, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Person2 { // same f1: Color1, @@ -466,7 +466,7 @@ fn named_enum() { White, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Item1 { f1: Color, f2: Color, @@ -481,7 +481,7 @@ fn named_enum() { last: i8, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Item2 { f1: Color, f2: Option<Color>, diff --git a/rust/tests/tests/compatible/test_struct_enum.rs b/rust/tests/tests/compatible/test_struct_enum.rs index 411556a4d..ececef218 100644 --- a/rust/tests/tests/compatible/test_struct_enum.rs +++ b/rust/tests/tests/compatible/test_struct_enum.rs @@ -188,7 +188,7 @@ fn nonexistent_struct() { #[test] fn option() { #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Animal { f1: Option<String>, f2: Option<String>, @@ -332,7 +332,7 @@ fn nullable_container() { #[test] fn inner_nullable() { #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Item1 { f1: Vec<Option<String>>, f2: HashSet<Option<i8>>, @@ -341,7 +341,7 @@ fn inner_nullable() { } #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Item2 { f1: Vec<String>, f2: HashSet<i8>, @@ -371,7 +371,7 @@ fn inner_nullable() { #[test] fn nullable_struct() { #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] pub struct Item { name: String, data: Vec<Option<String>>, @@ -379,7 +379,7 @@ fn nullable_struct() { } #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Person1 { f1: Item, f2: Option<Item>, @@ -388,7 +388,7 @@ fn nullable_struct() { } #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] pub struct Person2 { f1: Option<Item>, f2: Item, @@ -443,7 +443,7 @@ fn enum_without_payload() { Blue, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Person1 { f1: Color1, f2: Color1, @@ -456,7 +456,7 @@ fn enum_without_payload() { last: i8, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Person2 { // same f1: Color1, @@ -512,7 +512,7 @@ fn named_enum() { White, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Item1 { f1: Color, f2: Color, @@ -527,7 +527,7 @@ fn named_enum() { last: i8, } #[derive(ForyObject, Debug, PartialEq)] - #[fory_debug] + #[fory(debug)] struct Item2 { f1: Color, f2: Option<Color>, diff --git a/rust/tests/tests/test_cross_language.rs b/rust/tests/tests/test_cross_language.rs index d3957f844..1bc64434e 100644 --- a/rust/tests/tests/test_cross_language.rs +++ b/rust/tests/tests/test_cross_language.rs @@ -704,7 +704,7 @@ fn test_consistent_named() { } #[derive(ForyObject, Debug, PartialEq)] -#[fory_debug] +#[fory(debug)] struct VersionCheckStruct { f1: i32, f2: Option<String>, diff --git a/rust/tests/tests/test_debug.rs b/rust/tests/tests/test_debug.rs index 494640517..7c0e86369 100644 --- a/rust/tests/tests/test_debug.rs +++ b/rust/tests/tests/test_debug.rs @@ -26,7 +26,7 @@ use fory_core::serializer::struct_::{ }; #[derive(fory_derive::ForyObject)] -#[fory_debug] +#[fory(debug)] struct DebugSample { a: i32, b: String, diff --git a/rust/tests/tests/test_max_dyn_depth.rs b/rust/tests/tests/test_max_dyn_depth.rs index d1e777adf..521a11303 100644 --- a/rust/tests/tests/test_max_dyn_depth.rs +++ b/rust/tests/tests/test_max_dyn_depth.rs @@ -20,7 +20,7 @@ use fory_derive::ForyObject; use std::any::Any; #[derive(ForyObject, Debug)] -#[fory_debug] +#[fory(debug)] struct Container { value: i32, nested: Option<Box<dyn Any>>, diff --git a/rust/tests/tests/test_one_struct.rs b/rust/tests/tests/test_one_struct.rs index e50e0eb73..05bc51de0 100644 --- a/rust/tests/tests/test_one_struct.rs +++ b/rust/tests/tests/test_one_struct.rs @@ -24,7 +24,7 @@ fn test_simple() { // a single test for cargo expand and analysis: `cargo expand --test test_simple_struct 2>&1 > expanded.rs` // &["f7", "last", "f2", "f5", "f3", "f6", "f1"] #[derive(ForyObject, Debug)] - #[fory_debug] + #[fory(debug)] struct Animal1 { f1: HashMap<i8, Vec<i8>>, f2: String, --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
