facet_reflect/peek/
struct_.rs

1use facet_core::{Field, FieldError, FieldFlags, StructDef};
2
3use crate::Peek;
4use alloc::{vec, vec::Vec};
5
6/// Lets you read from a struct (implements read-only struct operations)
7#[derive(Clone, Copy)]
8pub struct PeekStruct<'mem, 'facet_lifetime> {
9    /// the underlying value
10    pub(crate) value: Peek<'mem, 'facet_lifetime>,
11
12    /// the definition of the struct!
13    pub(crate) def: StructDef,
14}
15
16impl core::fmt::Debug for PeekStruct<'_, '_> {
17    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
18        f.debug_struct("PeekStruct").finish_non_exhaustive()
19    }
20}
21
22impl<'mem, 'facet_lifetime> PeekStruct<'mem, 'facet_lifetime> {
23    /// Returns the struct definition
24    #[inline(always)]
25    pub fn def(&self) -> &StructDef {
26        &self.def
27    }
28
29    /// Returns the number of fields in this struct
30    #[inline(always)]
31    pub fn field_count(&self) -> usize {
32        self.def.fields.len()
33    }
34
35    /// Returns the value of the field at the given index
36    #[inline(always)]
37    pub fn field(&self, index: usize) -> Result<Peek<'mem, 'facet_lifetime>, FieldError> {
38        self.def
39            .fields
40            .get(index)
41            .map(|field| unsafe {
42                let field_data = self.value.data().field(field.offset);
43                Peek::unchecked_new(field_data, field.shape())
44            })
45            .ok_or(FieldError::IndexOutOfBounds {
46                index,
47                bound: self.def.fields.len(),
48            })
49    }
50
51    /// Gets the value of the field with the given name
52    #[inline]
53    pub fn field_by_name(&self, name: &str) -> Result<Peek<'mem, 'facet_lifetime>, FieldError> {
54        for (i, field) in self.def.fields.iter().enumerate() {
55            if field.name == name {
56                return self.field(i);
57            }
58        }
59        Err(FieldError::NoSuchField)
60    }
61}
62
63impl<'mem, 'facet_lifetime> HasFields<'mem, 'facet_lifetime> for PeekStruct<'mem, 'facet_lifetime> {
64    fn fields(&self) -> impl DoubleEndedIterator<Item = (Field, Peek<'mem, 'facet_lifetime>)> {
65        (0..self.def.fields.len())
66            .filter_map(move |i| self.field(i).ok().map(|value| (self.def.fields[i], value)))
67    }
68}
69
70/// Trait for types that have field methods
71///
72/// This trait allows code to be written generically over both structs and enums
73/// that provide field access and iteration capabilities.
74pub trait HasFields<'mem, 'facet_lifetime> {
75    /// Iterates over all fields in this type, providing both field metadata and value
76    fn fields(&self) -> impl DoubleEndedIterator<Item = (Field, Peek<'mem, 'facet_lifetime>)>;
77
78    /// Iterates over fields in this type that should be included when it is serialized
79    fn fields_for_serialize(
80        &self,
81    ) -> impl DoubleEndedIterator<Item = (Field, Peek<'mem, 'facet_lifetime>)> {
82        // This is a default implementation that filters out fields with `skip_serializing`
83        // attribute and handles field flattening.
84        self.fields()
85            .filter(|(field, peek)| !unsafe { field.should_skip_serializing(peek.data()) })
86            .flat_map(|(mut field, peek)| {
87                if field.flags.contains(FieldFlags::FLATTEN) {
88                    let mut flattened = Vec::new();
89                    if let Ok(struct_peek) = peek.into_struct() {
90                        struct_peek
91                            .fields_for_serialize()
92                            .for_each(|item| flattened.push(item));
93                    } else if let Ok(enum_peek) = peek.into_enum() {
94                        // normally we'd serialize to something like:
95                        //
96                        //   {
97                        //     "field_on_struct": {
98                        //       "VariantName": { "field_on_variant": "foo" }
99                        //     }
100                        //   }
101                        //
102                        // But since `field_on_struct` is flattened, instead we do:
103                        //
104                        //   {
105                        //     "VariantName": { "field_on_variant": "foo" }
106                        //   }
107                        field.name = enum_peek.active_variant().name;
108                        field.flattened = true;
109                        flattened.push((field, peek));
110                    } else {
111                        // TODO: fail more gracefully
112                        panic!("cannot flatten a {}", field.shape())
113                    }
114                    flattened.into_iter()
115                } else {
116                    vec![(field, peek)].into_iter()
117                }
118            })
119    }
120}