1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
use darling::{
    util::{Flag, Override, SpannedValue},
    FromAttributes,
};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use serde_derive_internals::ast as serde_ast;
use syn::{parse_quote, ExprPath, Ident};
use vector_config_common::validation::Validation;

use super::{
    util::{
        err_field_implicit_transparent, err_field_missing_description,
        find_delegated_serde_deser_ty, get_serde_default_value, try_extract_doc_title_description,
    },
    LazyCustomAttribute, Metadata,
};

/// A field of a container.
pub struct Field<'a> {
    original: &'a syn::Field,
    name: String,
    default_value: Option<ExprPath>,
    attrs: Attributes,
}

impl<'a> Field<'a> {
    /// Creates a new `Field<'a>` from the `serde`-derived information about the given field.
    pub fn from_ast(
        serde: &serde_ast::Field<'a>,
        is_virtual_newtype: bool,
        is_newtype_wrapper_field: bool,
    ) -> darling::Result<Field<'a>> {
        let original = serde.original;

        let name = serde.attrs.name().deserialize_name().to_string();
        let default_value = get_serde_default_value(&serde.ty, serde.attrs.default());

        Attributes::from_attributes(&original.attrs)
            .and_then(|attrs| {
                attrs.finalize(
                    serde,
                    &original.attrs,
                    is_virtual_newtype,
                    is_newtype_wrapper_field,
                )
            })
            .map(|attrs| Field {
                original,
                name,
                default_value,
                attrs,
            })
    }

    /// Name of the field, if any.
    ///
    /// Fields of tuple structs have no names.
    pub fn ident(&self) -> Option<&Ident> {
        self.original.ident.as_ref()
    }

    /// Type of the field.
    ///
    /// This is the as-defined type, and may not necessarily match the type we use for generating
    /// the schema: see `delegated_ty` for more information.
    pub fn ty(&self) -> &syn::Type {
        &self.original.ty
    }

    /// Delegated type of the field, if any.
    ///
    /// In some cases, helper types may be used to provide (de)serialization of types that cannot
    /// have `Deserialize`/`Serialize`, such as types in the standard library, or may be used to
    /// provide customized (de)serialization, such (de)serializing to and from a more human-readable
    /// version of a type, like time strings that let you specify `1s` or `1 hour`, and don't just
    /// force you to always specify the total number of seconds and nanoseconds, and so on.
    ///
    /// When these helper types are in use, we need to be able to understand what _they_ look like
    /// when serialized so that our generated schema accurately reflects what we expect to get
    /// during deserialization. Even though we may end up with a `T` in our configuration type, if
    /// we're (de)serializing it like a `U`, then we care about `U` when generating the schema, not `T`.
    ///
    /// We currently scope this type to helper types defined with `serde_with`: the reason is
    /// slightly verbose to explain (see `find_delegated_serde_deser_ty` for the details), but
    /// unless `serde_with` is being used, specifically the `#[serde_as(as = "...")]` helper
    /// attribute, then this will generally return `None`.
    ///
    /// If `#[serde_as(as = "...")]` _is_ being used, then `Some` is returned containing a reference
    /// to the delegated (de)serialization type. Again, see `find_delegated_serde_deser_ty` for more
    /// details about exactly what we look for to figure out if delegation is occurring, and the
    /// caveats around our approach.
    pub fn delegated_ty(&self) -> Option<&syn::Type> {
        self.attrs.delegated_ty.as_ref()
    }

    /// Name of the field when deserializing.
    ///
    /// This may be different than the name of the field itself depending on whether it has been
    /// altered with `serde` helper attributes i.e. `#[serde(rename = "...")]`.
    ///
    /// Additionally, for unnamed fields (tuple structs/variants), this will be the integer index of
    /// the field, formatted as a string.
    pub fn name(&self) -> &str {
        self.name.as_str()
    }

    /// Title of the field, if any.
    ///
    /// The title specifically refers to the headline portion of a doc comment. For example, if a
    /// field has the following doc comment:
    ///
    /// ```text
    /// /// My special field.
    /// ///
    /// /// Here's why it's special:
    /// /// ...
    /// field: bool,
    /// ```
    ///
    /// then the title would be `My special field`. If the doc comment only contained `My special
    /// field.`, then we would consider the title _empty_. See `description` for more details on
    /// detecting titles vs descriptions.
    pub fn title(&self) -> Option<&String> {
        self.attrs.title.as_ref()
    }

    /// Description of the field, if any.
    ///
    /// The description specifically refers to the body portion of a doc comment, or the headline if
    /// only a headline exists.. For example, if a field has the following doc comment:
    ///
    /// ```text
    /// /// My special field.
    /// ///
    /// /// Here's why it's special:
    /// /// ...
    /// field: bool,
    /// ```
    ///
    /// then the title would be everything that comes after `My special field`. If the doc comment
    /// only contained `My special field.`, then the description would be `My special field.`, and
    /// the title would be empty. In this way, the description will always be some port of a doc
    /// comment, depending on the formatting applied.
    ///
    /// This logic was chosen to mimic how Rust's own `rustdoc` tool works, where it will use the
    /// "title" portion as a high-level description for an item, only showing the title and
    /// description together when drilling down to the documentation for that specific item. JSON
    /// Schema supports both title and description for a schema, and so we expose both.
    pub fn description(&self) -> Option<&String> {
        self.attrs.description.as_ref()
    }

    /// Path to a function to call to generate a default value for the field, if any.
    ///
    /// This will boil down to something like `std::default::Default::default` or
    /// `name_of_in_scope_method_to_call`, where we generate code to actually call that path as a
    /// function to generate the default value we include in the schema for this field.
    pub fn default_value(&self) -> Option<ExprPath> {
        self.default_value.clone()
    }

    /// Whether or not the field is transparent.
    ///
    /// In some cases, namely scenarios involving newtype structs or enum tuple variants, it may be
    /// counter-intuitive to specify a title/description for a field. For example, having a newtype
    /// struct for defining systemd file descriptors requires a single internal integer field. The
    /// title/description of the newtype struct itself are sufficient from a documentation
    /// standpoint, but the procedural macro doesn't know that, and wants to enforce that we give
    /// the "field" -- the unnamed single integer field -- a title/description to ensure our
    /// resulting schema is fully specified.
    ///
    /// Applying the `#[configurable(transparent)]` helper attribute to a field will disable the
    /// title/description enforcement logic, allowing these types of newtype structs, or enum tuple
    /// variants, to simply document themselves at the container/variant level and avoid needing to
    /// document that inner field which itself needs no further title/description.
    pub fn transparent(&self) -> bool {
        self.attrs.transparent.is_present()
    }

    /// Whether or not the field is deprecated.
    ///
    /// Applying the `#[configurable(deprecated)]` helper attribute will mark this field as
    /// deprecated from the perspective of the resulting schema. It does not interact with Rust's
    /// standard `#[deprecated]` attribute, neither automatically applying it nor deriving the
    /// deprecation status of a field when it is present.
    pub fn deprecated(&self) -> bool {
        self.attrs.deprecated.is_some()
    }

    /// The deprecated message, if one has been set.
    pub fn deprecated_message(&self) -> Option<&String> {
        self.attrs
            .deprecated
            .as_ref()
            .and_then(|message| match message {
                Override::Inherit => None,
                Override::Explicit(message) => Some(message),
            })
    }

    /// Validation rules specific to the field, if any.
    ///
    /// Validation rules are applied to the resulting schema for this field on top of any default
    /// validation rules defined on the field type/delegated field type itself.
    pub fn validation(&self) -> &[Validation] {
        &self.attrs.validation
    }

    /// Whether or not this field is visible during either serialization or deserialization.
    ///
    /// This is derived from whether any of the `serde` visibility attributes are applied: `skip`,
    /// `skip_serializing, and `skip_deserializing`. Unless the field is skipped entirely, it will
    /// be considered visible and part of the schema.
    pub fn visible(&self) -> bool {
        self.attrs.visible
    }

    /// Whether or not to flatten the schema of this field into its container.
    ///
    /// This is derived from whether the `#[serde(flatten)]` helper attribute is present. When
    /// enabled, this will cause the field's schema to be flatten into the container's schema,
    /// mirroring how `serde` will lift the fields of the flattened field's type into the container
    /// type when (de)serializing.
    pub fn flatten(&self) -> bool {
        self.attrs.flatten
    }

    /// Metadata (custom attributes) for the field, if any.
    ///
    /// Attributes can take the shape of flags (`#[configurable(metadata(im_a_teapot))]`) or
    /// key/value pairs (`#[configurable(metadata(status = "beta"))]`) to allow rich, semantic
    /// metadata to be attached directly to fields.
    pub fn metadata(&self) -> impl Iterator<Item = LazyCustomAttribute> {
        self.attrs
            .metadata
            .clone()
            .into_iter()
            .flat_map(|metadata| metadata.attributes())
    }
}

impl<'a> ToTokens for Field<'a> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.original.to_tokens(tokens)
    }
}

#[derive(Debug, Default, FromAttributes)]
#[darling(default, attributes(configurable))]
struct Attributes {
    title: Option<String>,
    description: Option<String>,
    derived: SpannedValue<Flag>,
    transparent: SpannedValue<Flag>,
    deprecated: Option<Override<String>>,
    #[darling(skip)]
    visible: bool,
    #[darling(skip)]
    flatten: bool,
    #[darling(multiple)]
    metadata: Vec<Metadata>,
    #[darling(multiple)]
    validation: Vec<Validation>,
    #[darling(skip)]
    delegated_ty: Option<syn::Type>,
}

impl Attributes {
    fn finalize(
        mut self,
        field: &serde_ast::Field<'_>,
        forwarded_attrs: &[syn::Attribute],
        is_virtual_newtype: bool,
        is_newtype_wrapper_field: bool,
    ) -> darling::Result<Self> {
        // Derive any of the necessary fields from the `serde` side of things.
        self.visible = !field.attrs.skip_deserializing() || !field.attrs.skip_serializing();
        self.flatten = field.attrs.flatten();

        // We additionally attempt to extract a title/description from the forwarded doc attributes, if they exist.
        // Whether we extract both a title and description, or just description, is documented in more detail in
        // `try_extract_doc_title_description` itself.
        let (doc_title, doc_description) = try_extract_doc_title_description(forwarded_attrs);
        self.title = self.title.or(doc_title);
        self.description = self.description.or(doc_description);

        // If the field is part of a newtype wrapper -- it has the be the only field, and unnamed,
        // like `struct Foo(usize)` -- then we simply mark it as transparent.
        //
        // We do this because the container -- struct or enum variant -- will itself be required to
        // have a description. We never show the description of unnamed fields, anyways, as we defer
        // to using the description of the container. Simply marking this field as transparent will
        // keep the schema generation happy and avoid having to constantly specify `derived` or
        // `transparent` all over the place.
        if is_newtype_wrapper_field {
            // We additionally check here to see if transparent/derived as already set, as we want
            // to throw an error if they are. As we're going to forcefully mark the field as
            // transparent, there's no reason to allow setting derived/transparent manually, as it
            // only leads to boilerplate and potential confusion.
            if self.transparent.is_present() {
                return Err(err_field_implicit_transparent(&self.transparent.span()));
            }

            if self.derived.is_present() {
                return Err(err_field_implicit_transparent(&self.derived.span()));
            }

            self.transparent = SpannedValue::new(Flag::present(), Span::call_site());
        }

        // If no description was provided for the field, it is typically an error. There are few situations when this is
        // fine/valid, though:
        //
        // - the field is derived (`#[configurable(derived)]`)
        // - the field is transparent (`#[configurable(transparent)]`)
        // - the field is not visible (`#[serde(skip)]`, or `skip_serializing` plus `skip_deserializing`)
        // - the field is flattened (`#[serde(flatten)]`)
        // - the field is part of a virtual newtype
        // - the field is part of a newtype wrapper (struct/enum variant with a single unnamed field)
        //
        // If the field is derived, it means we're taking the description/title from the `Configurable` implementation of
        // the field type, which we can only do at runtime so we ignore it here. Similarly, if a field is transparent,
        // we're explicitly saying that our container is meant to essentially take on the schema of the field, rather
        // than the container being defined by the fields, if that makes sense. Derived and transparent fields are most
        // common in newtype structs and newtype variants in enums, where they're a `(T)`, and so the container acts
        // much like `T` itself.
        //
        // If the field is not visible, well, then, we're not inserting it in the schema and so requiring a description
        // or title makes no sense. Similarly, if a field is flattened, that field also won't exist in the schema as
        // we're lifting up all the fields from the type of the field itself, so again, requiring a description or title
        // makes no sense.
        //
        // If the field is part of a virtual newtype, this means the container has instructed `serde` to
        // (de)serialize it as some entirely different type. This means the original field will never show up in a
        // schema, because the schema of the thing being (de)serialized is some `T`, not `ContainerType`. Simply put,
        // like a field that is flattened or not visible, it makes no sense to require a description or title for fields
        // in a virtual newtype.
        if self.description.is_none()
            && !self.derived.is_present()
            && !self.transparent.is_present()
            && self.visible
            && !self.flatten
            && !is_virtual_newtype
        {
            return Err(err_field_missing_description(&field.original));
        }

        // Try and find the delegated (de)serialization type for this field, if it exists.
        self.delegated_ty = find_delegated_serde_deser_ty(forwarded_attrs).map(|virtual_ty| {
            // If there's a virtual type in use, we immediately transform it into our delegated
            // serialize wrapper, since we know we'll have to do that in a few different places
            // during codegen, so it's cleaner to do it here.
            let field_ty = field.ty;
            parse_quote! { ::vector_config::ser::Delegated<#field_ty, #virtual_ty> }
        });

        Ok(self)
    }
}