vector_config_macros/ast/
field.rs

1use darling::{
2    util::{Flag, Override, SpannedValue},
3    FromAttributes,
4};
5use proc_macro2::{Span, TokenStream};
6use quote::ToTokens;
7use serde_derive_internals::ast as serde_ast;
8use syn::{parse_quote, ExprPath, Ident};
9use vector_config_common::validation::Validation;
10
11use super::{
12    util::{
13        err_field_implicit_transparent, err_field_missing_description,
14        find_delegated_serde_deser_ty, get_serde_default_value, try_extract_doc_title_description,
15    },
16    LazyCustomAttribute, Metadata,
17};
18
19/// A field of a container.
20pub struct Field<'a> {
21    original: &'a syn::Field,
22    name: String,
23    default_value: Option<ExprPath>,
24    attrs: Attributes,
25}
26
27impl<'a> Field<'a> {
28    /// Creates a new `Field<'a>` from the `serde`-derived information about the given field.
29    pub fn from_ast(
30        serde: &serde_ast::Field<'a>,
31        is_virtual_newtype: bool,
32        is_newtype_wrapper_field: bool,
33    ) -> darling::Result<Field<'a>> {
34        let original = serde.original;
35
36        let name = serde.attrs.name().deserialize_name().to_string();
37        let default_value = get_serde_default_value(&serde.ty, serde.attrs.default());
38
39        Attributes::from_attributes(&original.attrs)
40            .and_then(|attrs| {
41                attrs.finalize(
42                    serde,
43                    &original.attrs,
44                    is_virtual_newtype,
45                    is_newtype_wrapper_field,
46                )
47            })
48            .map(|attrs| Field {
49                original,
50                name,
51                default_value,
52                attrs,
53            })
54    }
55
56    /// Name of the field, if any.
57    ///
58    /// Fields of tuple structs have no names.
59    pub fn ident(&self) -> Option<&Ident> {
60        self.original.ident.as_ref()
61    }
62
63    /// Type of the field.
64    ///
65    /// This is the as-defined type, and may not necessarily match the type we use for generating
66    /// the schema: see `delegated_ty` for more information.
67    pub fn ty(&self) -> &syn::Type {
68        &self.original.ty
69    }
70
71    /// Delegated type of the field, if any.
72    ///
73    /// In some cases, helper types may be used to provide (de)serialization of types that cannot
74    /// have `Deserialize`/`Serialize`, such as types in the standard library, or may be used to
75    /// provide customized (de)serialization, such (de)serializing to and from a more human-readable
76    /// version of a type, like time strings that let you specify `1s` or `1 hour`, and don't just
77    /// force you to always specify the total number of seconds and nanoseconds, and so on.
78    ///
79    /// When these helper types are in use, we need to be able to understand what _they_ look like
80    /// when serialized so that our generated schema accurately reflects what we expect to get
81    /// during deserialization. Even though we may end up with a `T` in our configuration type, if
82    /// we're (de)serializing it like a `U`, then we care about `U` when generating the schema, not `T`.
83    ///
84    /// We currently scope this type to helper types defined with `serde_with`: the reason is
85    /// slightly verbose to explain (see `find_delegated_serde_deser_ty` for the details), but
86    /// unless `serde_with` is being used, specifically the `#[serde_as(as = "...")]` helper
87    /// attribute, then this will generally return `None`.
88    ///
89    /// If `#[serde_as(as = "...")]` _is_ being used, then `Some` is returned containing a reference
90    /// to the delegated (de)serialization type. Again, see `find_delegated_serde_deser_ty` for more
91    /// details about exactly what we look for to figure out if delegation is occurring, and the
92    /// caveats around our approach.
93    pub fn delegated_ty(&self) -> Option<&syn::Type> {
94        self.attrs.delegated_ty.as_ref()
95    }
96
97    /// Name of the field when deserializing.
98    ///
99    /// This may be different than the name of the field itself depending on whether it has been
100    /// altered with `serde` helper attributes i.e. `#[serde(rename = "...")]`.
101    ///
102    /// Additionally, for unnamed fields (tuple structs/variants), this will be the integer index of
103    /// the field, formatted as a string.
104    pub fn name(&self) -> &str {
105        self.name.as_str()
106    }
107
108    /// Title of the field, if any.
109    ///
110    /// The title specifically refers to the headline portion of a doc comment. For example, if a
111    /// field has the following doc comment:
112    ///
113    /// ```text
114    /// /// My special field.
115    /// ///
116    /// /// Here's why it's special:
117    /// /// ...
118    /// field: bool,
119    /// ```
120    ///
121    /// then the title would be `My special field`. If the doc comment only contained `My special
122    /// field.`, then we would consider the title _empty_. See `description` for more details on
123    /// detecting titles vs descriptions.
124    pub fn title(&self) -> Option<&String> {
125        self.attrs.title.as_ref()
126    }
127
128    /// Description of the field, if any.
129    ///
130    /// The description specifically refers to the body portion of a doc comment, or the headline if
131    /// only a headline exists.. For example, if a field has the following doc comment:
132    ///
133    /// ```text
134    /// /// My special field.
135    /// ///
136    /// /// Here's why it's special:
137    /// /// ...
138    /// field: bool,
139    /// ```
140    ///
141    /// then the title would be everything that comes after `My special field`. If the doc comment
142    /// only contained `My special field.`, then the description would be `My special field.`, and
143    /// the title would be empty. In this way, the description will always be some port of a doc
144    /// comment, depending on the formatting applied.
145    ///
146    /// This logic was chosen to mimic how Rust's own `rustdoc` tool works, where it will use the
147    /// "title" portion as a high-level description for an item, only showing the title and
148    /// description together when drilling down to the documentation for that specific item. JSON
149    /// Schema supports both title and description for a schema, and so we expose both.
150    pub fn description(&self) -> Option<&String> {
151        self.attrs.description.as_ref()
152    }
153
154    /// Path to a function to call to generate a default value for the field, if any.
155    ///
156    /// This will boil down to something like `std::default::Default::default` or
157    /// `name_of_in_scope_method_to_call`, where we generate code to actually call that path as a
158    /// function to generate the default value we include in the schema for this field.
159    pub fn default_value(&self) -> Option<ExprPath> {
160        self.default_value.clone()
161    }
162
163    /// Whether or not the field is transparent.
164    ///
165    /// In some cases, namely scenarios involving newtype structs or enum tuple variants, it may be
166    /// counter-intuitive to specify a title/description for a field. For example, having a newtype
167    /// struct for defining systemd file descriptors requires a single internal integer field. The
168    /// title/description of the newtype struct itself are sufficient from a documentation
169    /// standpoint, but the procedural macro doesn't know that, and wants to enforce that we give
170    /// the "field" -- the unnamed single integer field -- a title/description to ensure our
171    /// resulting schema is fully specified.
172    ///
173    /// Applying the `#[configurable(transparent)]` helper attribute to a field will disable the
174    /// title/description enforcement logic, allowing these types of newtype structs, or enum tuple
175    /// variants, to simply document themselves at the container/variant level and avoid needing to
176    /// document that inner field which itself needs no further title/description.
177    pub fn transparent(&self) -> bool {
178        self.attrs.transparent.is_present()
179    }
180
181    /// Whether or not the field is deprecated.
182    ///
183    /// Applying the `#[configurable(deprecated)]` helper attribute will mark this field as
184    /// deprecated from the perspective of the resulting schema. It does not interact with Rust's
185    /// standard `#[deprecated]` attribute, neither automatically applying it nor deriving the
186    /// deprecation status of a field when it is present.
187    pub fn deprecated(&self) -> bool {
188        self.attrs.deprecated.is_some()
189    }
190
191    /// The deprecated message, if one has been set.
192    pub fn deprecated_message(&self) -> Option<&String> {
193        self.attrs
194            .deprecated
195            .as_ref()
196            .and_then(|message| match message {
197                Override::Inherit => None,
198                Override::Explicit(message) => Some(message),
199            })
200    }
201
202    /// Validation rules specific to the field, if any.
203    ///
204    /// Validation rules are applied to the resulting schema for this field on top of any default
205    /// validation rules defined on the field type/delegated field type itself.
206    pub fn validation(&self) -> &[Validation] {
207        &self.attrs.validation
208    }
209
210    /// Whether or not this field is visible during either serialization or deserialization.
211    ///
212    /// This is derived from whether any of the `serde` visibility attributes are applied: `skip`,
213    /// `skip_serializing, and `skip_deserializing`. Unless the field is skipped entirely, it will
214    /// be considered visible and part of the schema.
215    pub fn visible(&self) -> bool {
216        self.attrs.visible
217    }
218
219    /// Whether or not to flatten the schema of this field into its container.
220    ///
221    /// This is derived from whether the `#[serde(flatten)]` helper attribute is present. When
222    /// enabled, this will cause the field's schema to be flatten into the container's schema,
223    /// mirroring how `serde` will lift the fields of the flattened field's type into the container
224    /// type when (de)serializing.
225    pub fn flatten(&self) -> bool {
226        self.attrs.flatten
227    }
228
229    /// Metadata (custom attributes) for the field, if any.
230    ///
231    /// Attributes can take the shape of flags (`#[configurable(metadata(im_a_teapot))]`) or
232    /// key/value pairs (`#[configurable(metadata(status = "beta"))]`) to allow rich, semantic
233    /// metadata to be attached directly to fields.
234    pub fn metadata(&self) -> impl Iterator<Item = LazyCustomAttribute> {
235        self.attrs
236            .metadata
237            .clone()
238            .into_iter()
239            .flat_map(|metadata| metadata.attributes())
240    }
241}
242
243impl ToTokens for Field<'_> {
244    fn to_tokens(&self, tokens: &mut TokenStream) {
245        self.original.to_tokens(tokens)
246    }
247}
248
249#[derive(Debug, Default, FromAttributes)]
250#[darling(default, attributes(configurable))]
251struct Attributes {
252    title: Option<String>,
253    description: Option<String>,
254    derived: SpannedValue<Flag>,
255    transparent: SpannedValue<Flag>,
256    deprecated: Option<Override<String>>,
257    #[darling(skip)]
258    visible: bool,
259    #[darling(skip)]
260    flatten: bool,
261    #[darling(multiple)]
262    metadata: Vec<Metadata>,
263    #[darling(multiple)]
264    validation: Vec<Validation>,
265    #[darling(skip)]
266    delegated_ty: Option<syn::Type>,
267}
268
269impl Attributes {
270    fn finalize(
271        mut self,
272        field: &serde_ast::Field<'_>,
273        forwarded_attrs: &[syn::Attribute],
274        is_virtual_newtype: bool,
275        is_newtype_wrapper_field: bool,
276    ) -> darling::Result<Self> {
277        // Derive any of the necessary fields from the `serde` side of things.
278        self.visible = !field.attrs.skip_deserializing() || !field.attrs.skip_serializing();
279        self.flatten = field.attrs.flatten();
280
281        // We additionally attempt to extract a title/description from the forwarded doc attributes, if they exist.
282        // Whether we extract both a title and description, or just description, is documented in more detail in
283        // `try_extract_doc_title_description` itself.
284        let (doc_title, doc_description) = try_extract_doc_title_description(forwarded_attrs);
285        self.title = self.title.or(doc_title);
286        self.description = self.description.or(doc_description);
287
288        // If the field is part of a newtype wrapper -- it has the be the only field, and unnamed,
289        // like `struct Foo(usize)` -- then we simply mark it as transparent.
290        //
291        // We do this because the container -- struct or enum variant -- will itself be required to
292        // have a description. We never show the description of unnamed fields, anyways, as we defer
293        // to using the description of the container. Simply marking this field as transparent will
294        // keep the schema generation happy and avoid having to constantly specify `derived` or
295        // `transparent` all over the place.
296        if is_newtype_wrapper_field {
297            // We additionally check here to see if transparent/derived as already set, as we want
298            // to throw an error if they are. As we're going to forcefully mark the field as
299            // transparent, there's no reason to allow setting derived/transparent manually, as it
300            // only leads to boilerplate and potential confusion.
301            if self.transparent.is_present() {
302                return Err(err_field_implicit_transparent(&self.transparent.span()));
303            }
304
305            if self.derived.is_present() {
306                return Err(err_field_implicit_transparent(&self.derived.span()));
307            }
308
309            self.transparent = SpannedValue::new(Flag::present(), Span::call_site());
310        }
311
312        // If no description was provided for the field, it is typically an error. There are few situations when this is
313        // fine/valid, though:
314        //
315        // - the field is derived (`#[configurable(derived)]`)
316        // - the field is transparent (`#[configurable(transparent)]`)
317        // - the field is not visible (`#[serde(skip)]`, or `skip_serializing` plus `skip_deserializing`)
318        // - the field is flattened (`#[serde(flatten)]`)
319        // - the field is part of a virtual newtype
320        // - the field is part of a newtype wrapper (struct/enum variant with a single unnamed field)
321        //
322        // If the field is derived, it means we're taking the description/title from the `Configurable` implementation of
323        // the field type, which we can only do at runtime so we ignore it here. Similarly, if a field is transparent,
324        // we're explicitly saying that our container is meant to essentially take on the schema of the field, rather
325        // than the container being defined by the fields, if that makes sense. Derived and transparent fields are most
326        // common in newtype structs and newtype variants in enums, where they're a `(T)`, and so the container acts
327        // much like `T` itself.
328        //
329        // If the field is not visible, well, then, we're not inserting it in the schema and so requiring a description
330        // or title makes no sense. Similarly, if a field is flattened, that field also won't exist in the schema as
331        // we're lifting up all the fields from the type of the field itself, so again, requiring a description or title
332        // makes no sense.
333        //
334        // If the field is part of a virtual newtype, this means the container has instructed `serde` to
335        // (de)serialize it as some entirely different type. This means the original field will never show up in a
336        // schema, because the schema of the thing being (de)serialized is some `T`, not `ContainerType`. Simply put,
337        // like a field that is flattened or not visible, it makes no sense to require a description or title for fields
338        // in a virtual newtype.
339        if self.description.is_none()
340            && !self.derived.is_present()
341            && !self.transparent.is_present()
342            && self.visible
343            && !self.flatten
344            && !is_virtual_newtype
345        {
346            return Err(err_field_missing_description(&field.original));
347        }
348
349        // Try and find the delegated (de)serialization type for this field, if it exists.
350        self.delegated_ty = find_delegated_serde_deser_ty(forwarded_attrs).map(|virtual_ty| {
351            // If there's a virtual type in use, we immediately transform it into our delegated
352            // serialize wrapper, since we know we'll have to do that in a few different places
353            // during codegen, so it's cleaner to do it here.
354            let field_ty = field.ty;
355            parse_quote! { ::vector_config::ser::Delegated<#field_ty, #virtual_ty> }
356        });
357
358        Ok(self)
359    }
360}