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}