serialize_fields/
lib.rs

1//! # SerializeFields
2//!
3//! A Rust procedural macro that enables **dynamic field selection** for struct serialization.
4//! Control exactly which fields get serialized at runtime using a hierarchical field selector system.
5//!
6//! ## Features
7//!
8//! - 🎯 **Dynamic Field Selection**: Choose which fields to serialize at runtime
9//! - 🌳 **Hierarchical Selection**: Use dot notation for nested structs (`"user.profile.name"`)
10//! - 🔧 **Type Safe**: Compile-time validation of field paths
11//! - 🚀 **Zero Runtime Cost**: Only enabled fields are processed during serialization
12//! - 📦 **Serde Integration**: Works seamlessly with the serde ecosystem
13//! - 🔄 **Collection Support**: Handles `Vec`, `Option`, `HashMap`, and other containers
14//! - 🏗️ **Generic Architecture**: Single trait-based serialization implementation
15//!
16//! ## Quick Start
17//!
18//! ```rust
19//! use serialize_fields::{SerializeFields, SerializeFieldsTrait};
20//! use serde::{Serialize, Deserialize};
21//!
22//! #[derive(SerializeFields, Serialize, Deserialize)]
23//! struct User {
24//!     id: u32,
25//!     name: Option<String>,
26//!     email: Option<String>,
27//! }
28//!
29//! let user = User {
30//!     id: 123,
31//!     name: Some("Alice".to_string()),
32//!     email: Some("alice@example.com".to_string()),
33//! };
34//!
35//! // Create field selector using the trait method
36//! let mut fields = user.serialize_fields();
37//! fields.enable_dot_hierarchy("id");
38//! fields.enable_dot_hierarchy("name");
39//!
40//! // Serialize with selected fields only
41//! let json = serde_json::to_string(&SerializeFields(&user, &fields)).unwrap();
42//! // Output: {"id":123,"name":"Alice"}
43//! ```
44//!
45//! ## Advanced Usage
46//!
47//! ### Nested Structs
48//!
49//! ```rust
50//! # use serialize_fields::{SerializeFields, SerializeFieldsTrait};
51//! # use serde::{Serialize, Deserialize};
52//! #[derive(SerializeFields, Serialize, Deserialize)]
53//! struct User {
54//!     id: u32,
55//!     profile: UserProfile,
56//! }
57//!
58//! #[derive(SerializeFields, Serialize, Deserialize)]
59//! struct UserProfile {
60//!     bio: Option<String>,
61//!     avatar_url: Option<String>,
62//! }
63//!
64//! # let user = User {
65//! #     id: 123,
66//! #     profile: UserProfile {
67//! #         bio: Some("Software Engineer".to_string()),
68//! #         avatar_url: Some("https://example.com/avatar.jpg".to_string()),
69//! #     },
70//! # };
71//! let mut fields = user.serialize_fields();
72//! fields.enable_dot_hierarchy("id");
73//! fields.enable_dot_hierarchy("profile.bio");  // Nested field selection
74//!
75//! let json = serde_json::to_string(&SerializeFields(&user, &fields)).unwrap();
76//! // Output: {"id":123,"profile":{"bio":"Software Engineer"}}
77//! ```
78//!
79//! ### Dynamic Field Selection
80//!
81//! ```rust
82//! # use serialize_fields::{SerializeFields, SerializeFieldsTrait};
83//! # use serde::{Serialize, Deserialize};
84//! # #[derive(SerializeFields, Serialize, Deserialize)]
85//! # struct User { id: u32, name: Option<String>, email: Option<String> }
86//! # let user = User { id: 123, name: Some("Alice".to_string()), email: Some("alice@example.com".to_string()) };
87//! fn serialize_user_with_fields(user: &User, requested_fields: &[&str]) -> String {
88//!     let mut selector = user.serialize_fields();
89//!     
90//!     for field in requested_fields {
91//!         selector.enable_dot_hierarchy(field);
92//!     }
93//!     
94//!     serde_json::to_string(&SerializeFields(user, &selector)).unwrap()
95//! }
96//!
97//! // Usage: GET /users/123?fields=id,name
98//! let fields = vec!["id", "name"];
99//! let json = serialize_user_with_fields(&user, &fields);
100//! ```
101
102#![doc(html_root_url = "https://docs.rs/serialize_fields/0.2.3")]
103#![cfg_attr(docsrs, feature(doc_cfg))]
104
105// Re-export the derive macro
106pub use serialize_fields_macro::SerializeFields;
107
108mod macros;
109
110/// Trait for types that can provide field selectors for dynamic serialization.
111///
112/// This trait is automatically implemented by the `#[derive(SerializeFields)]` macro
113/// and provides both field selector creation and serialization functionality.
114///
115/// # Examples
116///
117/// ```rust
118/// # use serialize_fields::{SerializeFields as SerializeFieldsDerive, SerializeFieldsTrait};
119/// # use serde::{Serialize, Deserialize};
120/// #[derive(SerializeFieldsDerive, Serialize, Deserialize)]
121/// struct User {
122///     id: u32,
123///     name: String,
124/// }
125///
126/// # let user = User { id: 1, name: "Alice".to_string() };
127/// // Create a field selector using the trait method
128/// let mut fields = user.serialize_fields();
129/// fields.enable_dot_hierarchy("id");
130/// fields.enable_dot_hierarchy("name");
131/// ```
132pub trait SerializeFieldsTrait {
133    /// The type of field selector for this struct.
134    type FieldSelector: FieldSelector;
135
136    /// Create a new field selector for this type.
137    ///
138    /// This is a convenience method that's equivalent to calling
139    /// `{StructName}SerializeFieldSelector::new()` but provides a more
140    /// ergonomic API through the trait.
141    fn serialize_fields(&self) -> Self::FieldSelector;
142
143    /// Serialize this struct using the provided field selector.
144    ///
145    /// This method is called by the generic `Serialize` implementation
146    /// for `SerializeFields` and handles the actual serialization logic
147    /// with field filtering.
148    ///
149    /// # Arguments
150    ///
151    /// * `field_selector` - The field selector that determines which fields to include
152    /// * `serializer` - The serde serializer to use
153    fn serialize<__S>(
154        &self,
155        field_selector: &Self::FieldSelector,
156        __serializer: __S,
157    ) -> Result<__S::Ok, __S::Error>
158    where
159        __S: serde::Serializer;
160}
161
162/// A wrapper struct that combines data with a field selector for serialization.
163///
164/// This is the core type that enables dynamic field selection. It wraps your data
165/// and a field selector, implementing `Serialize` to only include enabled fields.
166///
167/// # Type Parameters
168///
169/// - `T`: The type of data being serialized
170/// - `S`: The type of field selector (typically generated by the derive macro)
171///
172/// # Examples
173///
174/// ```rust
175/// # use serialize_fields::{SerializeFields, SerializeFields as SerializeFieldsDerive};
176/// # use serde::{Serialize, Deserialize};
177/// # #[derive(SerializeFieldsDerive, Serialize, Deserialize)]
178/// # struct User { id: u32, name: String }
179/// # let user = User { id: 1, name: "Alice".to_string() };
180/// # let mut selector = UserSerializeFieldSelector::new();
181/// # selector.enable_dot_hierarchy("id");
182/// let wrapper = SerializeFields(&user, &selector);
183/// let json = serde_json::to_string(&wrapper).unwrap();
184/// ```
185pub struct SerializeFields<'a, T, S>(pub &'a T, pub &'a S);
186
187impl<'a, T, S> serde::Serialize for SerializeFields<'a, T, S>
188where
189    T: SerializeFieldsTrait<FieldSelector = S>,
190    S: FieldSelector,
191{
192    fn serialize<Se>(&self, serializer: Se) -> Result<Se::Ok, Se::Error>
193    where
194        Se: serde::Serializer,
195    {
196        self.0.serialize(self.1, serializer)
197    }
198}
199
200// Generic implementation for Vec<T> where T implements SerializeFieldsTrait
201impl<'a, T, S> serde::Serialize for SerializeFields<'a, Vec<T>, S>
202where
203    T: SerializeFieldsTrait<FieldSelector = S>,
204    S: FieldSelector,
205{
206    fn serialize<Se>(&self, serializer: Se) -> Result<Se::Ok, Se::Error>
207    where
208        Se: serde::Serializer,
209    {
210        use serde::ser::SerializeSeq;
211
212        let data = self.0;
213        let field_selector = self.1;
214
215        let mut seq = serializer.serialize_seq(Some(data.len()))?;
216
217        for item in data {
218            seq.serialize_element(&SerializeFields(item, field_selector))?;
219        }
220
221        seq.end()
222    }
223}
224
225// Generic implementation for Option<T> where T implements SerializeFieldsTrait
226impl<'a, T, S> serde::Serialize for SerializeFields<'a, Option<T>, S>
227where
228    T: SerializeFieldsTrait<FieldSelector = S>,
229    S: FieldSelector,
230{
231    fn serialize<Se>(&self, serializer: Se) -> Result<Se::Ok, Se::Error>
232    where
233        Se: serde::Serializer,
234    {
235        let data = self.0;
236        let field_selector = self.1;
237
238        match data {
239            Some(inner) => SerializeFields(inner, field_selector).serialize(serializer),
240            None => serializer.serialize_none(),
241        }
242    }
243}
244
245// implement JsonSchema for SerializeFields<T, S> where T implements JsonSchema
246#[cfg(feature = "schemars")]
247impl<'a, T, S> schemars::JsonSchema for SerializeFields<'a, T, S>
248where
249    T: schemars::JsonSchema,
250    S: FieldSelector,
251{
252    // Required methods
253    fn schema_name() -> std::borrow::Cow<'static, str> {
254        T::schema_name()
255    }
256    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
257        T::json_schema(generator)
258    }
259
260    // Provided methods
261    fn always_inline_schema() -> bool {
262        T::always_inline_schema()
263    }
264    fn inline_schema() -> bool {
265        T::inline_schema()
266    }
267    fn schema_id() -> std::borrow::Cow<'static, str> {
268        T::schema_id()
269    }
270}
271
272/// Helper trait for field selectors to provide common functionality.
273///
274/// This trait is automatically implemented for all generated field selectors.
275pub trait FieldSelector {
276    /// Create a new selector with all fields disabled.
277    fn new() -> Self;
278
279    /// Enable a field using dot notation.
280    ///
281    /// # Examples
282    ///
283    /// ```ignore
284    /// selector.enable_dot_hierarchy("name");           // Simple field
285    /// selector.enable_dot_hierarchy("profile.bio");    // Nested field
286    /// selector.enable_dot_hierarchy("posts.title");    // Field in collection
287    /// ```
288    fn enable_dot_hierarchy(&mut self, field: &str);
289
290    /// Enable a field using a slice of field names.
291    ///
292    /// This is useful when you already have the field path split.
293    ///
294    /// # Examples
295    ///
296    /// ```ignore
297    /// selector.enable(&["name"]);                  // Simple field
298    /// selector.enable(&["profile", "bio"]);       // Nested field
299    /// ```
300    fn enable(&mut self, field_hierarchy: &[&str]);
301}
302
303/// Utility functions for working with field selectors.
304pub mod utils {
305    /// Parse a comma-separated list of field names.
306    ///
307    /// This is useful for parsing query parameters or configuration strings.
308    ///
309    /// # Examples
310    ///
311    /// ```rust
312    /// use serialize_fields::utils::parse_field_list;
313    ///
314    /// let fields = parse_field_list("id,name,profile.bio");
315    /// assert_eq!(fields, vec!["id", "name", "profile.bio"]);
316    /// ```
317    pub fn parse_field_list(fields: &str) -> Vec<&str> {
318        fields
319            .split(',')
320            .map(|s| s.trim())
321            .filter(|s| !s.is_empty())
322            .collect()
323    }
324
325    /// Create a field selector from a list of field names.
326    ///
327    /// This is a convenience function that combines parsing and enabling fields.
328    ///
329    /// # Examples
330    ///
331    /// ```ignore
332    /// use serialize_fields::utils::create_selector_from_list;
333    ///
334    /// let selector: UserSerializeFieldSelector =
335    ///     create_selector_from_list("id,name,profile.bio");
336    /// ```
337    pub fn create_selector_from_list<T>(fields: &str) -> T
338    where
339        T: crate::FieldSelector,
340    {
341        let mut selector = T::new();
342        for field in parse_field_list(fields) {
343            selector.enable_dot_hierarchy(field);
344        }
345        selector
346    }
347}