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 b234a1f51 refactor(rust): merge fory_debug into fory macro attr (#2883)
b234a1f51 is described below
commit b234a1f518238888c230b6fda58504ed53156a8a
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]