Skip to main content

facet_reflect/peek/
fields.rs

1use core::ops::Range;
2
3use alloc::borrow::Cow;
4use facet_core::{Field, Variant};
5
6use crate::Peek;
7use alloc::{string::String, vec, vec::Vec};
8
9use super::{PeekEnum, PeekStruct, PeekTuple};
10
11/// A field item with runtime state for serialization.
12///
13/// This wraps a static `Field` with additional runtime state that can be modified
14/// during iteration (e.g., for flattened enums where the field name becomes the variant name,
15/// or for flattened maps where entries become synthetic fields).
16#[derive(Clone, Debug)]
17pub struct FieldItem {
18    /// The underlying static field definition (None for flattened map entries)
19    pub field: Option<Field>,
20
21    /// Runtime-determined name (may differ from field.name for flattened enums/maps)
22    pub name: Cow<'static, str>,
23
24    /// Result of applying any field-level `#[facet(rename = ...)]` or container-level
25    /// `#[facet(rename_all = ...)]` attributes. If `None`, no such attribute was encountered.
26    pub rename: Option<Cow<'static, str>>,
27
28    /// Whether this field was flattened from an enum (variant name used as key)
29    pub flattened: bool,
30
31    /// Whether this is a text variant (html::text or xml::text) from a flattened enum.
32    /// When true, the value should be serialized as raw text without an element wrapper.
33    pub is_text_variant: bool,
34}
35
36impl FieldItem {
37    /// Create a new FieldItem from a Field, using the field's name
38    #[inline]
39    pub const fn new(field: Field) -> Self {
40        Self {
41            name: Cow::Borrowed(field.name),
42            rename: match field.rename {
43                Some(r) => Some(Cow::Borrowed(r)),
44                None => None,
45            },
46            field: Some(field),
47            flattened: false,
48            is_text_variant: false,
49        }
50    }
51
52    /// Create a flattened enum field item with a custom name (the variant name)
53    #[inline]
54    pub fn flattened_enum(field: Field, variant: &Variant) -> Self {
55        Self {
56            name: Cow::Borrowed(variant.name),
57            rename: match variant.rename {
58                Some(r) => Some(Cow::Borrowed(r)),
59                None => None,
60            },
61            field: Some(field),
62            flattened: true,
63            is_text_variant: variant.is_text(),
64        }
65    }
66
67    /// Create a flattened map entry field item with a dynamic key
68    #[inline]
69    pub const fn flattened_map_entry(key: String) -> Self {
70        Self {
71            name: Cow::Owned(key),
72            rename: None,
73            field: None,
74            flattened: true,
75            is_text_variant: false,
76        }
77    }
78
79    /// Returns the effective name for this field item, preferring the rename over the original name
80    #[inline]
81    pub fn effective_name(&self) -> &str {
82        match &self.rename {
83            Some(r) => r.as_ref(),
84            None => self.name.as_ref(),
85        }
86    }
87}
88
89/// Trait for types that have field methods
90///
91/// This trait allows code to be written generically over both structs and enums
92/// that provide field access and iteration capabilities.
93pub trait HasFields<'mem, 'facet> {
94    /// Iterates over all fields in this type, providing both field metadata and value
95    fn fields(&self) -> FieldIter<'mem, 'facet>;
96
97    /// Iterates over fields in this type that should be included when it is serialized.
98    ///
99    /// This respects `#[facet(skip_serializing_if = ...)]` and `#[facet(skip_all_unless_truthy)]`
100    /// predicates, which is correct for self-describing formats like JSON where skipped fields
101    /// can be reconstructed from the schema.
102    fn fields_for_serialize(&self) -> FieldsForSerializeIter<'mem, 'facet> {
103        FieldsForSerializeIter {
104            stack: vec![FieldsForSerializeIterState::Fields(self.fields())],
105            skip_predicates: true,
106        }
107    }
108
109    /// Iterates over fields for serialization to positional binary formats.
110    ///
111    /// Unlike [`fields_for_serialize`](Self::fields_for_serialize), this ignores
112    /// `skip_serializing_if` predicates (including those from `skip_all_unless_truthy`).
113    /// This is necessary for binary formats like postcard where fields are identified by
114    /// position rather than name - skipping fields would cause a mismatch between
115    /// serialized and expected field positions during deserialization.
116    ///
117    /// Note: This still respects unconditional `#[facet(skip)]` and `#[facet(skip_serializing)]`
118    /// attributes, as those indicate fields that should never be serialized regardless of format.
119    fn fields_for_binary_serialize(&self) -> FieldsForSerializeIter<'mem, 'facet> {
120        FieldsForSerializeIter {
121            stack: vec![FieldsForSerializeIterState::Fields(self.fields())],
122            skip_predicates: false,
123        }
124    }
125}
126
127/// An iterator over all the fields of a struct or enum. See [`HasFields::fields`]
128pub struct FieldIter<'mem, 'facet> {
129    state: FieldIterState<'mem, 'facet>,
130    range: Range<usize>,
131}
132
133enum FieldIterState<'mem, 'facet> {
134    Struct(PeekStruct<'mem, 'facet>),
135    Tuple(PeekTuple<'mem, 'facet>),
136    Enum {
137        peek_enum: PeekEnum<'mem, 'facet>,
138        fields: &'static [Field],
139    },
140}
141
142impl<'mem, 'facet> FieldIter<'mem, 'facet> {
143    #[inline]
144    pub(crate) const fn new_struct(struct_: PeekStruct<'mem, 'facet>) -> Self {
145        Self {
146            range: 0..struct_.ty.fields.len(),
147            state: FieldIterState::Struct(struct_),
148        }
149    }
150
151    #[inline]
152    pub(crate) fn new_enum(enum_: PeekEnum<'mem, 'facet>) -> Self {
153        // Get the fields of the active variant
154        let variant = match enum_.active_variant() {
155            Ok(v) => v,
156            Err(e) => panic!("Cannot get active variant: {e:?}"),
157        };
158        let fields = &variant.data.fields;
159
160        Self {
161            range: 0..fields.len(),
162            state: FieldIterState::Enum {
163                peek_enum: enum_,
164                fields,
165            },
166        }
167    }
168
169    #[inline]
170    pub(crate) const fn new_tuple(tuple: PeekTuple<'mem, 'facet>) -> Self {
171        Self {
172            range: 0..tuple.len(),
173            state: FieldIterState::Tuple(tuple),
174        }
175    }
176
177    fn get_field_by_index(&self, index: usize) -> Option<(Field, Peek<'mem, 'facet>)> {
178        match self.state {
179            FieldIterState::Struct(peek_struct) => {
180                let field = peek_struct.ty.fields.get(index).copied()?;
181                let value = peek_struct.field(index).ok()?;
182                Some((field, value))
183            }
184            FieldIterState::Tuple(peek_tuple) => {
185                let field = peek_tuple.ty.fields.get(index).copied()?;
186                let value = peek_tuple.field(index)?;
187                Some((field, value))
188            }
189            FieldIterState::Enum { peek_enum, fields } => {
190                // Get the field definition
191                let field = fields[index];
192                // Get the field value
193                let field_value = match peek_enum.field(index) {
194                    Ok(Some(v)) => v,
195                    Ok(None) => return None,
196                    Err(e) => panic!("Cannot get field: {e:?}"),
197                };
198                // Return the field definition and value
199                Some((field, field_value))
200            }
201        }
202    }
203}
204
205impl<'mem, 'facet> Iterator for FieldIter<'mem, 'facet> {
206    type Item = (Field, Peek<'mem, 'facet>);
207
208    #[inline]
209    fn next(&mut self) -> Option<Self::Item> {
210        loop {
211            let index = self.range.next()?;
212
213            let Some(field) = self.get_field_by_index(index) else {
214                continue;
215            };
216
217            return Some(field);
218        }
219    }
220
221    #[inline]
222    fn size_hint(&self) -> (usize, Option<usize>) {
223        self.range.size_hint()
224    }
225}
226
227impl DoubleEndedIterator for FieldIter<'_, '_> {
228    #[inline]
229    fn next_back(&mut self) -> Option<Self::Item> {
230        loop {
231            let index = self.range.next_back()?;
232
233            let Some(field) = self.get_field_by_index(index) else {
234                continue;
235            };
236
237            return Some(field);
238        }
239    }
240}
241
242impl ExactSizeIterator for FieldIter<'_, '_> {}
243
244/// An iterator over the fields of a struct or enum that should be serialized. See [`HasFields::fields_for_serialize`]
245pub struct FieldsForSerializeIter<'mem, 'facet> {
246    stack: Vec<FieldsForSerializeIterState<'mem, 'facet>>,
247    /// Whether to evaluate skip_serializing_if predicates.
248    /// Set to false for binary formats where all fields must be present.
249    skip_predicates: bool,
250}
251
252enum FieldsForSerializeIterState<'mem, 'facet> {
253    /// Normal field iteration
254    Fields(FieldIter<'mem, 'facet>),
255    /// A single flattened enum item to yield
256    FlattenedEnum {
257        field_item: Option<FieldItem>,
258        value: Peek<'mem, 'facet>,
259    },
260    /// Iterating over a flattened map's entries
261    FlattenedMap {
262        map_iter: super::PeekMapIter<'mem, 'facet>,
263    },
264    /// Iterating over a flattened list of enums (`Vec<Enum>`)
265    FlattenedEnumList {
266        field: Field,
267        list_iter: super::PeekListIter<'mem, 'facet>,
268    },
269}
270
271impl<'mem, 'facet> Iterator for FieldsForSerializeIter<'mem, 'facet> {
272    type Item = (FieldItem, Peek<'mem, 'facet>);
273
274    fn next(&mut self) -> Option<Self::Item> {
275        loop {
276            let state = self.stack.pop()?;
277
278            match state {
279                FieldsForSerializeIterState::FlattenedEnum { field_item, value } => {
280                    // Yield the flattened enum item (only once)
281                    if let Some(item) = field_item {
282                        return Some((item, value));
283                    }
284                    // Already yielded, continue to next state
285                    continue;
286                }
287                FieldsForSerializeIterState::FlattenedMap { mut map_iter } => {
288                    // Iterate over map entries, yielding each as a synthetic field
289                    if let Some((key_peek, value_peek)) = map_iter.next() {
290                        // Push iterator back for more entries
291                        self.stack
292                            .push(FieldsForSerializeIterState::FlattenedMap { map_iter });
293                        // Get the key as a string using Display trait
294                        // This works for String, SmolStr, SmartString, CompactString, etc.
295                        if key_peek.shape().vtable.has_display() {
296                            use alloc::string::ToString;
297                            let key_str = key_peek.to_string();
298                            let field_item = FieldItem::flattened_map_entry(key_str);
299                            return Some((field_item, value_peek));
300                        }
301                        // Skip entries with non-string-like keys
302                        continue;
303                    }
304                    // Map exhausted, continue to next state
305                    continue;
306                }
307                FieldsForSerializeIterState::FlattenedEnumList {
308                    field,
309                    mut list_iter,
310                } => {
311                    // Iterate over list items, yielding each enum as a flattened field
312                    if let Some(item_peek) = list_iter.next() {
313                        // Push iterator back for more items
314                        self.stack
315                            .push(FieldsForSerializeIterState::FlattenedEnumList {
316                                field,
317                                list_iter,
318                            });
319
320                        // Each item should be an enum - get its variant
321                        if let Ok(enum_peek) = item_peek.into_enum() {
322                            let variant = enum_peek
323                                .active_variant()
324                                .expect("Failed to get active variant");
325                            let field_item = FieldItem::flattened_enum(field, variant);
326
327                            // Get the inner value based on variant kind
328                            use facet_core::StructKind;
329                            match variant.data.kind {
330                                StructKind::Unit => {
331                                    // Unit variants - yield field with unit value
332                                    return Some((field_item, item_peek));
333                                }
334                                StructKind::TupleStruct | StructKind::Tuple
335                                    if variant.data.fields.len() == 1 =>
336                                {
337                                    // Newtype variant - yield the inner value directly
338                                    let inner_value = enum_peek
339                                        .field(0)
340                                        .expect("Failed to get variant field")
341                                        .expect("Newtype variant should have field 0");
342                                    return Some((field_item, inner_value));
343                                }
344                                StructKind::TupleStruct
345                                | StructKind::Tuple
346                                | StructKind::Struct => {
347                                    // Multi-field tuple or struct variant - yield the enum itself
348                                    return Some((field_item, item_peek));
349                                }
350                            }
351                        }
352                        // Skip non-enum items
353                        continue;
354                    }
355                    // List exhausted, continue to next state
356                    continue;
357                }
358                FieldsForSerializeIterState::Fields(mut fields) => {
359                    let Some((field, peek)) = fields.next() else {
360                        continue;
361                    };
362                    self.stack.push(FieldsForSerializeIterState::Fields(fields));
363
364                    // Check if we should skip this field.
365                    // For binary formats (skip_predicates = false), only check unconditional flags.
366                    // For text formats (skip_predicates = true), also evaluate predicates.
367                    let should_skip = if self.skip_predicates {
368                        let data = peek.data();
369                        unsafe { field.should_skip_serializing(data) }
370                    } else {
371                        field.should_skip_serializing_unconditional()
372                    };
373
374                    if should_skip {
375                        continue;
376                    }
377
378                    if field.is_flattened() {
379                        if let Ok(struct_peek) = peek.into_struct() {
380                            self.stack.push(FieldsForSerializeIterState::Fields(
381                                FieldIter::new_struct(struct_peek),
382                            ))
383                        } else if let Ok(enum_peek) = peek.into_enum() {
384                            // normally we'd serialize to something like:
385                            //
386                            //   {
387                            //     "field_on_struct": {
388                            //       "VariantName": { "field_on_variant": "foo" }
389                            //     }
390                            //   }
391                            //
392                            // But since `field_on_struct` is flattened, instead we do:
393                            //
394                            //   {
395                            //     "VariantName": { "field_on_variant": "foo" }
396                            //   }
397                            //
398                            // To achieve this, we emit the variant name as the field key
399                            // and the variant's inner value (not the whole enum) as the value.
400                            let variant = enum_peek
401                                .active_variant()
402                                .expect("Failed to get active variant");
403                            let field_item = FieldItem::flattened_enum(field, variant);
404
405                            // Get the inner value based on variant kind
406                            use facet_core::StructKind;
407                            let inner_value = match variant.data.kind {
408                                StructKind::Unit => {
409                                    // Unit variants have no inner value - skip
410                                    continue;
411                                }
412                                StructKind::TupleStruct | StructKind::Tuple
413                                    if variant.data.fields.len() == 1 =>
414                                {
415                                    // Newtype variant - yield the inner value directly
416                                    enum_peek
417                                        .field(0)
418                                        .expect("Failed to get variant field")
419                                        .expect("Newtype variant should have field 0")
420                                }
421                                StructKind::TupleStruct
422                                | StructKind::Tuple
423                                | StructKind::Struct => {
424                                    // Multi-field tuple or struct variant - push fields iterator
425                                    self.stack.push(FieldsForSerializeIterState::FlattenedEnum {
426                                        field_item: Some(field_item),
427                                        value: peek,
428                                    });
429                                    // The serializer will handle enum variant serialization
430                                    // which will emit the variant's fields/array properly
431                                    continue;
432                                }
433                            };
434
435                            self.stack.push(FieldsForSerializeIterState::FlattenedEnum {
436                                field_item: Some(field_item),
437                                value: inner_value,
438                            });
439                        } else if let Ok(map_peek) = peek.into_map() {
440                            // Flattened map - emit entries as synthetic fields
441                            self.stack.push(FieldsForSerializeIterState::FlattenedMap {
442                                map_iter: map_peek.iter(),
443                            });
444                        } else if let Ok(option_peek) = peek.into_option() {
445                            // Option<T> where T is a struct, enum, or map
446                            // If Some, flatten the inner value; if None, skip entirely
447                            if let Some(inner_peek) = option_peek.value() {
448                                if let Ok(struct_peek) = inner_peek.into_struct() {
449                                    self.stack.push(FieldsForSerializeIterState::Fields(
450                                        FieldIter::new_struct(struct_peek),
451                                    ))
452                                } else if let Ok(enum_peek) = inner_peek.into_enum() {
453                                    let variant = enum_peek
454                                        .active_variant()
455                                        .expect("Failed to get active variant");
456                                    let field_item = FieldItem::flattened_enum(field, variant);
457
458                                    // Get the inner value based on variant kind
459                                    use facet_core::StructKind;
460                                    let inner_value = match variant.data.kind {
461                                        StructKind::Unit => {
462                                            // Unit variants have no inner value - skip
463                                            continue;
464                                        }
465                                        StructKind::TupleStruct | StructKind::Tuple
466                                            if variant.data.fields.len() == 1 =>
467                                        {
468                                            // Newtype variant - yield the inner value directly
469                                            enum_peek
470                                                .field(0)
471                                                .expect("Failed to get variant field")
472                                                .expect("Newtype variant should have field 0")
473                                        }
474                                        StructKind::TupleStruct
475                                        | StructKind::Tuple
476                                        | StructKind::Struct => {
477                                            // Multi-field tuple or struct variant
478                                            self.stack.push(
479                                                FieldsForSerializeIterState::FlattenedEnum {
480                                                    field_item: Some(field_item),
481                                                    value: inner_peek,
482                                                },
483                                            );
484                                            continue;
485                                        }
486                                    };
487
488                                    self.stack.push(FieldsForSerializeIterState::FlattenedEnum {
489                                        field_item: Some(field_item),
490                                        value: inner_value,
491                                    });
492                                } else if let Ok(map_peek) = inner_peek.into_map() {
493                                    self.stack.push(FieldsForSerializeIterState::FlattenedMap {
494                                        map_iter: map_peek.iter(),
495                                    });
496                                } else {
497                                    panic!(
498                                        "cannot flatten Option<{}> - inner type must be struct, enum, or map",
499                                        inner_peek.shape()
500                                    )
501                                }
502                            }
503                            // If None, we just skip - don't emit any fields
504                        } else if let Ok(list_peek) = peek.into_list() {
505                            // Vec<Enum> - emit each enum item as a flattened field
506                            self.stack
507                                .push(FieldsForSerializeIterState::FlattenedEnumList {
508                                    field,
509                                    list_iter: list_peek.iter(),
510                                });
511                        } else {
512                            // TODO: fail more gracefully
513                            panic!("cannot flatten a {}", field.shape())
514                        }
515                    } else {
516                        return Some((FieldItem::new(field), peek));
517                    }
518                }
519            }
520        }
521    }
522}