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}