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}