Skip to main content

fory_derive/
lib.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! # Fory Derive Macros
19//!
20//! This crate provides procedural macros for the Fory serialization framework.
21//! It generates serialization and deserialization code for Rust types.
22//! Most applications should import these macros from the `fory` facade crate,
23//! which also provides the runtime API used by generated code. Direct
24//! `fory-derive` usage is for crates that intentionally depend on the
25//! lower-level `fory-core` runtime crate.
26//!
27//! ## Available Macros
28//!
29//! ### `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, `#[derive(ForyUnion)]`
30//!
31//! Generates `Serializer` implementations for structs, pure enums, and tagged
32//! unions with payload variants.
33//!
34//! **Supported Types:**
35//! - `ForyStruct`: named, tuple, and unit structs
36//! - `ForyEnum`: pure unit enums
37//! - `ForyUnion`: enums with payload variants
38//!
39//! **Example:**
40//! ```rust
41//! use fory_derive::{ForyEnum, ForyStruct};
42//! use std::collections::HashMap;
43//!
44//! #[derive(ForyStruct, Debug, PartialEq)]
45//! struct Person {
46//!     name: String,
47//!     age: i32,
48//!     address: Address,
49//!     hobbies: Vec<String>,
50//!     metadata: HashMap<String, String>,
51//! }
52//!
53//! #[derive(ForyStruct, Debug, PartialEq)]
54//! struct Address {
55//!     street: String,
56//!     city: String,
57//! }
58//!
59//! #[derive(ForyEnum, Debug, PartialEq, Default)]
60//! enum Status {
61//!     #[default]
62//!     Active,
63//!     Inactive,
64//!     Suspended,
65//! }
66//! ```
67//!
68//! ### `#[derive(ForyRow)]`
69//!
70//! Generates Standard Row Format serialization and borrowed field views for a
71//! named struct. The macro implements `RowValue` and the root `Row` marker.
72//! Enums, unions, tuple structs, and unit structs are rejected at compile time.
73//!
74//! **Supported Types:**
75//! - Fixed values: `bool`, `i8`, `i16`, `i32`, `i64`, `f32`, `f64`, `Date`,
76//!   `Timestamp`, and `Duration`
77//! - Variable values: `String` and `&str`, binary `Vec<u8>` and `&[u8]`, fixed
78//!   and variable arrays, `BTreeMap`, and other derived row structs
79//! - `Option<T>` for nullable fields and array elements
80//! - Every field type must implement `RowValue`
81//!
82//! **Example:**
83//! ```rust
84//! use fory_core::error::Error;
85//! use fory_core::row::{from_row, to_row};
86//! use fory_derive::ForyRow;
87//!
88//! #[derive(ForyRow)]
89//! struct UserProfile {
90//!     id: i64,
91//!     username: String,
92//!     email: Option<String>,
93//! }
94//!
95//! # fn main() -> Result<(), Error> {
96//! let bytes = to_row(&UserProfile {
97//!     id: 7,
98//!     username: "fory".to_owned(),
99//!     email: None,
100//! })?;
101//! let view = from_row::<UserProfile>(&bytes)?;
102//! assert_eq!(view.id()?, 7);
103//! assert_eq!(view.username()?, "fory");
104//! assert_eq!(view.email()?, None);
105//! # Ok(())
106//! # }
107//! ```
108//!
109//! ## Generated Code
110//!
111//! ### For `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, and `#[derive(ForyUnion)]`
112//!
113//! The macro generates:
114//! - `Serializer` trait implementation
115//! - Serialization methods for writing data to buffers
116//! - Deserialization methods for reading data from buffers
117//! - Type ID management for cross-language compatibility
118//!
119//! ### For `#[derive(ForyRow)]`
120//!
121//! The macro generates:
122//! - A `RowValue` implementation and a root `Row` marker implementation
123//! - A borrowed view type whose visibility matches the source struct
124//! - `RowView` backing-byte access and cheap `Copy`/`Clone` views
125//! - One declaration-order field method preserving each source field's visibility
126//! - Field methods returning `Result<<Field as RowValue>::View<'_>, Error>`
127//!
128//! ## Attributes
129//!
130//! - **`#[fory(debug)]` / `#[fory(debug = true)]`**: Enables per-field debug instrumentation
131//!   for the annotated struct, allowing you to install custom hooks via
132//!   `fory_core::serializer::struct_`.
133//! - **`#[fory(evolving = false)]`**: Disables compatible struct type IDs for the annotated
134//!   struct, forcing STRUCT/NAMED_STRUCT even when compatible mode is enabled.
135//! - **`#[fory(skip)]`**: Marks an individual field (or enum variant) to be ignored by the
136//!   generated serializer, retaining compatibility with previous releases.
137//! - **`#[fory(generate_default)]`**: Enables the macro to generate `Default` implementation.
138//!   By default, `ForyStruct` does NOT generate `impl Default` to avoid conflicts with existing
139//!   `Default` implementations. This attribute is not valid with `target`.
140//! - **`#[fory(target = path::Type)]`**: Makes the derived declaration an external structural
141//!   serializer for the target type. Generated code accesses and constructs the target directly;
142//!   the serializer declaration itself is never instantiated.
143//! - **`#[fory(with = SerializerType)]`**: Selects a serializer whose target is the exact field
144//!   value node. Use carrier serializers for exact wrapper or container nodes, and use `list`,
145//!   `map`, or `tuple` metadata to select serializers recursively at child nodes.
146//! - **`#[fory(default)]`**: Marks the fallible deserialization default `ForyUnion` variant.
147//!   `ForyUnion` requires exactly one default variant.
148//!
149//! ## Field Types
150//!
151//! The object-format derives support a wide range of field types:
152//!
153//! **Primitive Types:**
154//! - `bool`, `i8`, `i16`, `i32`, `i64`, `f32`, `f64`
155//! - `String`
156//! - `Vec<u8>` for binary data
157//!
158//! **Collections:**
159//! - `Vec<T>` where `T` implements the appropriate trait
160//! - `HashMap<K, V>` and `BTreeMap<K, V>` where keys and values implement the trait
161//! - `Option<T>` for nullable values
162//!
163//! **Date/Time:**
164//! - `fory::Date`
165//! - `fory::Timestamp`
166//! - `fory::Duration`
167//! - `chrono::NaiveDate`, `chrono::NaiveDateTime`, and `chrono::Duration` when the `chrono` feature is enabled
168//!
169//! **Custom Types:**
170//! - Any type that implements `Serializer`
171//!
172//! `ForyRow` uses the separate, exact type set documented under its macro
173//! section. A row field implements `RowValue`; only derived structs, arrays,
174//! and maps implement the root `Row` marker.
175//!
176//! Derived structs, enums, and unions can be used behind
177//! `Arc<dyn Any + Send + Sync>` when the concrete type satisfies `Send + Sync`.
178//! Known non-`Send + Sync` field types such as `Rc<T>` and `RefCell<T>` are not
179//! eligible for that carrier.
180//!
181//! ## Usage with Fory
182//!
183//! After deriving the macros, you can use the types with the Fory serialization
184//! framework:
185//!
186//! ```rust
187//! use fory_core::{fory::Fory, error::Error};
188//! use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
189//!
190//! #[derive(ForyStruct, Debug, PartialEq)]
191//! struct MyData {
192//!     value: i32,
193//!     text: String,
194//! }
195//!
196//! fn main() -> Result<(), Error> {
197//!     let mut fory = Fory::builder().xlang(true).build();
198//!     fory.register_by_name::<MyData>("example.MyData")?;
199//!     
200//!     let data = MyData {
201//!         value: 42,
202//!         text: "Hello, Fory!".to_string(),
203//!     };
204//!     
205//!     let serialized = fory.serialize(&data)?;
206//!     let deserialized: MyData = fory.deserialize(&serialized)?;
207//!     
208//!     assert_eq!(data, deserialized);
209//!     Ok(())
210//! }
211//! ```
212//!
213//! ## Performance Considerations
214//!
215//! - **`Fory`**: Best for complex object graphs with references and nested structures
216//! - **`ForyRow`**: Provides lazy, borrowed access to Standard Row Format data
217//! - Both macros generate optimized code with minimal runtime overhead
218
219use fory_row::derive_row;
220use proc_macro::TokenStream;
221use syn::{
222    parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Fields, LitBool, Type,
223};
224
225mod fory_row;
226mod object;
227mod runtime_root;
228mod util;
229
230/// Derive macro for struct serialization.
231#[proc_macro_derive(ForyStruct, attributes(fory))]
232pub fn proc_macro_derive_fory_struct(input: proc_macro::TokenStream) -> TokenStream {
233    let input = parse_macro_input!(input as DeriveInput);
234    if !matches!(input.data, Data::Struct(_)) {
235        return syn::Error::new(
236            input.ident.span(),
237            "ForyStruct can only be derived for structs; use ForyEnum for pure enums or ForyUnion for data-carrying enums",
238        )
239        .into_compile_error()
240        .into();
241    }
242    derive_serializer(input)
243}
244
245/// Derive macro for pure enum serialization.
246#[proc_macro_derive(ForyEnum, attributes(fory))]
247pub fn proc_macro_derive_fory_enum(input: proc_macro::TokenStream) -> TokenStream {
248    let input = parse_macro_input!(input as DeriveInput);
249    let Data::Enum(data_enum) = &input.data else {
250        return syn::Error::new(input.ident.span(), "ForyEnum can only be derived for enums")
251            .into_compile_error()
252            .into();
253    };
254    if data_enum
255        .variants
256        .iter()
257        .any(|variant| !matches!(variant.fields, Fields::Unit))
258    {
259        return syn::Error::new(
260            input.ident.span(),
261            "ForyEnum is only for pure unit enums; use ForyUnion for enum variants with payloads",
262        )
263        .into_compile_error()
264        .into();
265    }
266    derive_serializer(input)
267}
268
269/// Derives serialization for data-carrying Rust enums.
270///
271/// Xlang-compatible unit and single-payload variants use the Fory `UNION`
272/// representation. Native multi-field tuple and named variants use the native
273/// `ENUM` representation.
274#[proc_macro_derive(ForyUnion, attributes(fory))]
275pub fn proc_macro_derive_fory_union(input: proc_macro::TokenStream) -> TokenStream {
276    let input = parse_macro_input!(input as DeriveInput);
277    if let Err(err) = object::derive_union::validate_input(&input) {
278        return err.into_compile_error().into();
279    }
280    derive_serializer(input)
281}
282
283fn derive_serializer(input: DeriveInput) -> TokenStream {
284    let attrs = match parse_fory_attrs(&input.attrs) {
285        Ok(attrs) => attrs,
286        Err(err) => return err.into_compile_error().into(),
287    };
288    let runtime_root = match runtime_root::resolve_runtime_root() {
289        Ok(root) => root,
290        Err(err) => return err.into_compile_error().into(),
291    };
292
293    object::derive_serializer(&input, attrs, runtime_root)
294}
295
296/// Derive macro for Standard Row Format serialization.
297///
298/// This macro accepts named structs whose fields implement `RowValue`. It
299/// implements `RowValue` and the root `Row` marker, preserves field declaration
300/// order, and generates a borrowed view with field methods that return `Result`.
301///
302/// # Example
303///
304/// ```rust
305/// use fory_core::error::Error;
306/// use fory_core::row::{from_row, to_row};
307/// use fory_derive::ForyRow;
308///
309/// #[derive(ForyRow)]
310/// struct UserProfile {
311///     id: i64,
312///     username: String,
313///     email: Option<String>,
314/// }
315///
316/// # fn main() -> Result<(), Error> {
317/// let bytes = to_row(&UserProfile {
318///     id: 7,
319///     username: "fory".to_owned(),
320///     email: None,
321/// })?;
322/// let view = from_row::<UserProfile>(&bytes)?;
323/// assert_eq!(view.username()?, "fory");
324/// # Ok(())
325/// # }
326/// ```
327#[proc_macro_derive(ForyRow)]
328pub fn proc_macro_derive_fory_row(input: proc_macro::TokenStream) -> TokenStream {
329    let input = parse_macro_input!(input as DeriveInput);
330    let runtime_root = match runtime_root::resolve_runtime_root() {
331        Ok(root) => root,
332        Err(err) => return err.into_compile_error().into(),
333    };
334    derive_row(&input, runtime_root)
335}
336
337/// Parsed fory attributes
338pub(crate) struct ForyAttrs {
339    pub debug_enabled: bool,
340    pub generate_default: bool,
341    pub evolving: Option<bool>,
342    pub target: Option<Type>,
343}
344
345/// Parse fory attributes and return ForyAttrs
346fn parse_fory_attrs(attrs: &[Attribute]) -> syn::Result<ForyAttrs> {
347    let mut debug_flag: Option<bool> = None;
348    let mut generate_default_flag: Option<bool> = None;
349    let mut evolving_flag: Option<bool> = None;
350    let mut target: Option<Type> = None;
351
352    for attr in attrs {
353        if attr.path().is_ident("fory") {
354            attr.parse_nested_meta(|meta| {
355                if meta.path.is_ident("debug") {
356                    let value = if meta.input.is_empty() {
357                        true
358                    } else {
359                        let lit: LitBool = meta.value()?.parse()?;
360                        lit.value
361                    };
362                    debug_flag = match debug_flag {
363                        Some(existing) if existing != value => {
364                            return Err(syn::Error::new(
365                                meta.path.span(),
366                                "conflicting `debug` attribute values",
367                            ));
368                        }
369                        Some(_) => debug_flag,
370                        None => Some(value),
371                    };
372                } else if meta.path.is_ident("generate_default") {
373                    let value = if meta.input.is_empty() {
374                        true
375                    } else {
376                        let lit: LitBool = meta.value()?.parse()?;
377                        lit.value
378                    };
379                    generate_default_flag = match generate_default_flag {
380                        Some(existing) if existing != value => {
381                            return Err(syn::Error::new(
382                                meta.path.span(),
383                                "conflicting `generate_default` attribute values",
384                            ));
385                        }
386                        Some(_) => generate_default_flag,
387                        None => Some(value),
388                    };
389                } else if meta.path.is_ident("evolving") {
390                    let value = if meta.input.is_empty() {
391                        true
392                    } else {
393                        let lit: LitBool = meta.value()?.parse()?;
394                        lit.value
395                    };
396                    evolving_flag = match evolving_flag {
397                        Some(existing) if existing != value => {
398                            return Err(syn::Error::new(
399                                meta.path.span(),
400                                "conflicting `evolving` attribute values",
401                            ));
402                        }
403                        Some(_) => evolving_flag,
404                        None => Some(value),
405                    };
406                } else if meta.path.is_ident("target") {
407                    if target.is_some() {
408                        return Err(syn::Error::new(
409                            meta.path.span(),
410                            "duplicate `target` attribute",
411                        ));
412                    }
413                    target = Some(meta.value()?.parse()?);
414                } else {
415                    return Err(meta.error("unsupported type-level fory attribute"));
416                }
417                Ok(())
418            })?;
419        }
420    }
421
422    if let Some(target) = &target {
423        if generate_default_flag == Some(true) {
424            return Err(syn::Error::new(
425                target.span(),
426                "`generate_default` is not valid for an external structural serializer",
427            ));
428        }
429    }
430
431    Ok(ForyAttrs {
432        debug_enabled: debug_flag.unwrap_or(false),
433        generate_default: generate_default_flag.unwrap_or(false),
434        evolving: evolving_flag,
435        target,
436    })
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use quote::ToTokens;
443    use syn::parse_quote;
444
445    #[test]
446    fn parses_external_target() {
447        let input: DeriveInput = parse_quote! {
448            #[fory(target = external::User)]
449            struct UserSerializer {
450                name: String,
451            }
452        };
453        let attrs = parse_fory_attrs(&input.attrs).unwrap();
454        assert_eq!(
455            attrs.target.unwrap().to_token_stream().to_string(),
456            "external :: User"
457        );
458    }
459
460    #[test]
461    fn rejects_external_std_default() {
462        let input: DeriveInput = parse_quote! {
463            #[fory(target = external::User, generate_default)]
464            struct UserSerializer {
465                name: String,
466            }
467        };
468        let err = match parse_fory_attrs(&input.attrs) {
469            Ok(_) => panic!("external structural serializers must reject `generate_default`"),
470            Err(err) => err,
471        };
472        assert!(err.to_string().contains("generate_default` is not valid"));
473    }
474}