Skip to main content

facet_reflect/peek/
fields.rs

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