vector_config_macros/ast/
container.rs

1// Code generated by the `darling` derive macro triggers a clippy lint.
2// https://github.com/TedDriggs/darling/issues/293
3#![allow(clippy::manual_unwrap_or_default)]
4
5use std::collections::HashSet;
6
7use darling::{error::Accumulator, util::Flag, FromAttributes};
8use serde_derive_internals::{ast as serde_ast, Ctxt, Derive};
9use syn::{
10    DeriveInput, ExprPath, GenericArgument, Generics, Ident, PathArguments, PathSegment, Type,
11    TypeParam,
12};
13
14use super::{
15    util::{
16        err_serde_failed, get_serde_default_value, try_extract_doc_title_description,
17        DarlingResultIterator,
18    },
19    Data, Field, LazyCustomAttribute, Metadata, Style, Tagging, Variant,
20};
21
22const ERR_NO_ENUM_TUPLES: &str = "enum variants cannot be tuples (multiple unnamed fields)";
23const ERR_NO_ENUM_VARIANT_DESCRIPTION: &str = "enum variants must have a description i.e. `/// This is a description` or `#[configurable(description = \"This is a description...\")]`";
24const ERR_ENUM_UNTAGGED_DUPLICATES: &str = "enum variants must be unique in style/shape when in untagged mode i.e. there cannot be multiple unit variants, or tuple variants with the same fields, etc";
25const ERR_NO_UNIT_STRUCTS: &str = "unit structs are not supported by `Configurable`";
26const ERR_MISSING_DESC: &str = "all structs/enums must have a description i.e. `/// This is a description` or `#[configurable(description = \"This is a description...\")]`";
27const ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION: &str = "any container using `from`/`try_from`/`into` via `#[serde(...)]` must do so symmetrically i.e. the from/into types must match";
28const ERR_SERDE_TYPE_CONVERSION_FROM_TRY_FROM: &str = "`#[serde(from)]` and `#[serde(try_from)]` cannot be identical, as it is impossible for an infallible conversion from T to also be fallible";
29
30/// A source data structure annotated with `#[derive(Configurable)]`, parsed into an internal
31/// representation.
32pub struct Container<'a> {
33    original: &'a DeriveInput,
34    name: String,
35    default_value: Option<ExprPath>,
36    data: Data<'a>,
37    tagging: Option<Tagging>,
38    virtual_newtype: Option<Type>,
39    attrs: Attributes,
40}
41
42impl<'a> Container<'a> {
43    /// Creates a new `Container<'a>` from the raw derive macro input.
44    pub fn from_derive_input(input: &'a DeriveInput) -> darling::Result<Container<'a>> {
45        // We can't do anything unless `serde` can also handle this container. We specifically only care about
46        // deserialization here, because the schema tells us what we can _give_ to Vector.
47        let context = Ctxt::new();
48        let serde = match serde_ast::Container::from_ast(&context, input, Derive::Deserialize) {
49            Some(serde) => {
50                // This `serde_derive_internals` helper will panic if `check` isn't _always_ called, so we also have to
51                // call it on the success path.
52                context
53                    .check()
54                    .expect("should not have errors if container was parsed successfully");
55                Ok(serde)
56            }
57            None => Err(err_serde_failed(context)),
58        }?;
59
60        let mut accumulator = Accumulator::default();
61
62        // Check if we're dealing with a "virtual" newtype.
63        //
64        // In some cases, types may (de)serialize themselves as another type, which is entirely normal... but
65        // they may do this with `serde` helper attributes rather than with a newtype wrapper or manually
66        // converting between types.
67        //
68        // For types doing this, it could be entirely irrelevant to document all of the internal fields, or at
69        // least enforce documenting them, because they don't truly represent the actual schema and all that
70        // might get used is the documentation on the type having `Configurable` derived.
71        //
72        // All of that said, we check to see if the `from`, `try_from`, or `into` helper attributes are being
73        // used from `serde`, and make sure the transformation is symmetric (it has to
74        // deserialize from T and serialize to T, no halfsies) since we can't express a schema that's
75        // half-and-half. Assuming it passes this requirement, we track the actual (de)serialized type and use
76        // that for our schema generation instead.
77        let virtual_newtype = if serde.attrs.type_from().is_some()
78            || serde.attrs.type_try_from().is_some()
79            || serde.attrs.type_into().is_some()
80        {
81            // if any of these are set, we start by checking `into`. If it's set, then that's fine, and we
82            // continue verifying. Otherwise, it implies that `from`/`try_from` are set, and we only allow
83            // symmetric conversions.
84            if let Some(into_ty) = serde.attrs.type_into() {
85                // Figure out which of `from` and `try_from` are set. Both cannot be set, because either the
86                // types are different -- which means asymmetric conversion -- or they're both the same, which
87                // would be a logical fallacy since you can't have a fallible conversion from T if you already
88                // have an infallible conversion from T.
89                //
90                // Similar, at least one of them must be set, otherwise that's an asymmetric conversion.
91                match (serde.attrs.type_from(), serde.attrs.type_try_from()) {
92                    (None, None) => {
93                        accumulator.push(
94                            darling::Error::custom(ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION)
95                                .with_span(&serde.ident),
96                        );
97                        None
98                    }
99                    (Some(_), Some(_)) => {
100                        accumulator.push(
101                            darling::Error::custom(ERR_SERDE_TYPE_CONVERSION_FROM_TRY_FROM)
102                                .with_span(&serde.ident),
103                        );
104                        None
105                    }
106                    (Some(from_ty), None) | (None, Some(from_ty)) => {
107                        if into_ty == from_ty {
108                            Some(into_ty.clone())
109                        } else {
110                            accumulator.push(
111                                darling::Error::custom(ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION)
112                                    .with_span(&serde.ident),
113                            );
114                            None
115                        }
116                    }
117                }
118            } else {
119                accumulator.push(
120                    darling::Error::custom(ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION)
121                        .with_span(&serde.ident),
122                );
123                None
124            }
125        } else {
126            None
127        };
128
129        // Once we have the `serde` side of things, we need to collect our own specific attributes for the container
130        // and map things to our own `Container`.
131        Attributes::from_attributes(&input.attrs)
132            .and_then(|attrs| attrs.finalize(&input.attrs))
133            // We successfully parsed the derive input through both `serde` itself and our own attribute parsing, so
134            // build our data container based on whether or not we have a struct, enum, and do any necessary
135            // validation, etc.
136            .and_then(|attrs| {
137                let tagging: Tagging = serde.attrs.tag().into();
138
139                let (data, is_enum) = match serde.data {
140                    serde_ast::Data::Enum(variants) => {
141                        let variants = variants
142                            .iter()
143                            // When an item is marked as being skipped -- `#[serde(skip)]` -- we
144                            // want to filter out variants that are skipped entirely, because
145                            // otherwise they have to meet all the criteria (doc comment, etc)
146                            // despite the fact they won't be part of the configuration schema
147                            // anyways, and we can't filter it out after the below step because all
148                            // we get is the errors until they can be validated completely.
149                            .filter(|variant| {
150                                !variant.attrs.skip_deserializing()
151                                    && !variant.attrs.skip_serializing()
152                            })
153                            .map(|variant| {
154                                Variant::from_ast(
155                                    variant,
156                                    tagging.clone(),
157                                    virtual_newtype.is_some(),
158                                )
159                            })
160                            .collect_darling_results(&mut accumulator);
161
162                        // Check the generated variants for conformance. We do this at a per-variant and per-enum level.
163                        // Not all enum variant styles are compatible with the various tagging types that `serde`
164                        // supports, and additionally, we have some of our own constraints that we want to enforce.
165                        for variant in &variants {
166                            // We don't support tuple variants.
167                            if variant.style() == Style::Tuple {
168                                accumulator.push(
169                                    darling::Error::custom(ERR_NO_ENUM_TUPLES).with_span(variant),
170                                );
171                            }
172
173                            // All variants must have a description, except for untagged enums.
174                            //
175                            // This allows untagged enums used for "(de)serialize as A, B, or C"
176                            // purposes to avoid needless titles/descriptions when their fields will
177                            // implicitly provide that.
178                            if variant.description().is_none() && tagging != Tagging::None {
179                                accumulator.push(
180                                    darling::Error::custom(ERR_NO_ENUM_VARIANT_DESCRIPTION)
181                                        .with_span(variant),
182                                );
183                            }
184                        }
185
186                        // If we're in untagged mode, there can be no duplicate variants.
187                        if tagging == Tagging::None {
188                            for (i, variant) in variants.iter().enumerate() {
189                                for (k, other_variant) in variants.iter().enumerate() {
190                                    if variant == other_variant && i != k {
191                                        accumulator.push(
192                                            darling::Error::custom(ERR_ENUM_UNTAGGED_DUPLICATES)
193                                                .with_span(variant),
194                                        );
195                                    }
196                                }
197                            }
198                        }
199
200                        (Data::Enum(variants), true)
201                    }
202                    serde_ast::Data::Struct(style, fields) => match style {
203                        serde_ast::Style::Struct
204                        | serde_ast::Style::Tuple
205                        | serde_ast::Style::Newtype => {
206                            let is_newtype_wrapper_field =
207                                matches!(style, serde_ast::Style::Newtype);
208                            let fields = fields
209                                .iter()
210                                .map(|field| {
211                                    Field::from_ast(
212                                        field,
213                                        virtual_newtype.is_some(),
214                                        is_newtype_wrapper_field,
215                                    )
216                                })
217                                .collect_darling_results(&mut accumulator);
218
219                            (Data::Struct(style.into(), fields), false)
220                        }
221                        serde_ast::Style::Unit => {
222                            // This is a little ugly but we can't drop the accumulator without finishing it, otherwise
223                            // it will panic to let us know we didn't assert whether there were errors or not... so add
224                            // our error and just return a dummy value.
225                            accumulator
226                                .push(darling::Error::custom(ERR_NO_UNIT_STRUCTS).with_span(input));
227                            (Data::Struct(Style::Unit, Vec::new()), false)
228                        }
229                    },
230                };
231
232                // All containers must have a description: no ifs, ands, or buts.
233                //
234                // The compile-time errors are a bit too inscrutable otherwise, and inscrutable errors are not very
235                // helpful when using procedural macros.
236                if attrs.description.is_none() {
237                    accumulator
238                        .push(darling::Error::custom(ERR_MISSING_DESC).with_span(&serde.ident));
239                }
240
241                let original = input;
242                let name = serde.attrs.name().deserialize_name().to_string();
243                let default_value = get_serde_default_value(&serde.ident, serde.attrs.default());
244
245                let container = Container {
246                    original,
247                    name,
248                    default_value,
249                    data,
250                    virtual_newtype,
251                    tagging: is_enum.then_some(tagging),
252                    attrs,
253                };
254
255                accumulator.finish_with(container)
256            })
257    }
258
259    /// Ident of the container.
260    ///
261    /// This is simply the name or type of a struct/enum, but is not parsed directly as a type via
262    /// `syn`, only an `Ident`.
263    pub fn ident(&self) -> &Ident {
264        &self.original.ident
265    }
266
267    /// Generics for the container, if any.
268    pub fn generics(&self) -> &Generics {
269        &self.original.generics
270    }
271
272    /// Data for the container.
273    ///
274    /// This would be the fields of a struct, or the variants for an enum.
275    pub fn data(&self) -> &Data {
276        &self.data
277    }
278
279    /// Name of the container when deserializing.
280    ///
281    /// This may be different than the name of the container itself depending on whether it has been
282    /// altered with `serde` helper attributes i.e. `#[serde(rename = "...")]`.
283    pub fn name(&self) -> &str {
284        self.name.as_str()
285    }
286
287    /// Title of the container, if any.
288    ///
289    /// The title specifically refers to the headline portion of a doc comment. For example, if a
290    /// struct has the following doc comment:
291    ///
292    /// ```text
293    /// /// My special struct.
294    /// ///
295    /// /// Here's why it's special:
296    /// /// ...
297    /// struct Wrapper(u64);
298    /// ```
299    ///
300    /// then the title would be `My special struct`. If the doc comment only contained `My special
301    /// struct.`, then we would consider the title _empty_. See `description` for more details on
302    /// detecting titles vs descriptions.
303    pub fn title(&self) -> Option<&String> {
304        self.attrs.title.as_ref()
305    }
306
307    /// Description of the struct, if any.
308    ///
309    /// The description specifically refers to the body portion of a doc comment, or the headline if
310    /// only a headline exists.. For example, if a struct has the following doc comment:
311    ///
312    /// ```text
313    /// /// My special struct.
314    /// ///
315    /// /// Here's why it's special:
316    /// /// ...
317    /// struct Wrapper(u64);
318    /// ```
319    ///
320    /// then the title would be everything that comes after `My special struct`. If the doc comment
321    /// only contained `My special struct.`, then the description would be `My special struct.`, and
322    /// the title would be empty. In this way, the description will always be some port of a doc
323    /// comment, depending on the formatting applied.
324    ///
325    /// This logic was chosen to mimic how Rust's own `rustdoc` tool works, where it will use the
326    /// "title" portion as a high-level description for an item, only showing the title and
327    /// description together when drilling down to the documentation for that specific item. JSON
328    /// Schema supports both title and description for a schema, and so we expose both.
329    pub fn description(&self) -> Option<&String> {
330        self.attrs.description.as_ref()
331    }
332
333    /// Virtual type of this container, if any.
334    ///
335    /// In some cases, a type may be representable by an entirely different type, and then converted
336    /// to the desired type using the common `TryFrom`/`From`/`Into` conversion traits. This is a
337    /// common pattern with `serde` to re-use existing conversion logic (such as taking a raw string
338    /// and parsing it to see if it's a valid regular expression, and so on) but represents a
339    /// divergence between the type we're generating a schema for and the actual type that will be
340    /// getting (de)serialized.
341    ///
342    /// When we detect a container with the right `serde` helper attributes (see the code in
343    /// `from_derive_input` for details), we switch to treating this container as, for the purpose
344    /// of schema generation, having the type specified by those helper attributes.
345    pub fn virtual_newtype(&self) -> Option<Type> {
346        self.virtual_newtype.clone()
347    }
348
349    /// Tagging mode of this container.
350    ///
351    /// When the container is an enum, `Some(..)` will be returned, where the value can be any of
352    /// the four modes supported by `serde`: external, internal, adjacent, or none (untagged).
353    ///
354    /// When the container is a struct, `None` is returned.
355    pub fn tagging(&self) -> Option<&Tagging> {
356        self.tagging.as_ref()
357    }
358
359    /// Path to a function to call to generate a default value for the container, if any.
360    ///
361    /// This will boil down to something like `std::default::Default::default` or
362    /// `name_of_in_scope_method_to_call`, where we generate code to actually call that path as a
363    /// function to generate the default value we include in the schema for this container.
364    pub fn default_value(&self) -> Option<ExprPath> {
365        self.default_value.clone()
366    }
367
368    /// Whether or not the container is deprecated.
369    ///
370    /// Applying the `#[configurable(deprecated)]` helper attribute will mark this container as
371    /// deprecated from the perspective of the resulting schema. It does not interact with Rust's
372    /// standard `#[deprecated]` attribute, neither automatically applying it nor deriving the
373    /// deprecation status of a field when it is present.
374    pub fn deprecated(&self) -> bool {
375        self.attrs.deprecated.is_present()
376    }
377
378    /// Metadata (custom attributes) for the container, if any.
379    ///
380    /// Attributes can take the shape of flags (`#[configurable(metadata(im_a_teapot))]`) or
381    /// key/value pairs (`#[configurable(metadata(status = "beta"))]`) to allow rich, semantic
382    /// metadata to be attached directly to containers.
383    pub fn metadata(&self) -> impl Iterator<Item = LazyCustomAttribute> {
384        self.attrs
385            .metadata
386            .clone()
387            .into_iter()
388            .flat_map(|metadata| metadata.attributes())
389    }
390
391    /// Gets the generic types that are used within fields or variants that are part of the schema.
392    ///
393    /// In order to ensure we can allow for a maximally flexible `Configurable` trait, we add bounds to generic types that are
394    /// present on derived containers so that bounds don't need to be added on the actual container itself, essentially
395    /// avoiding declarations like `pub struct Foo<T> where T: Configurable {...}`.
396    ///
397    /// We contain this logic here as we only care about generic type parameters that are present on fields that will be
398    /// included in the schema, so skipped fields shouldn't have bounds added, and so on.
399    pub fn generic_field_types(&self) -> Vec<TypeParam> {
400        let mut generic_types = Vec::new();
401
402        let field_types = match &self.data {
403            Data::Struct(_, fields) => fields
404                .iter()
405                .filter(|f| f.visible())
406                .filter_map(|f| get_generic_type_param_idents(f.ty()))
407                .flatten()
408                .collect::<HashSet<_>>(),
409            Data::Enum(variants) => variants
410                .iter()
411                .filter(|v| v.visible())
412                .flat_map(|v| v.fields().iter())
413                .filter_map(|f| get_generic_type_param_idents(f.ty()))
414                .flatten()
415                .collect::<HashSet<_>>(),
416        };
417
418        for type_param in self.original.generics.type_params() {
419            if field_types.contains(&type_param.ident) {
420                generic_types.push(type_param.clone());
421            }
422        }
423
424        generic_types
425    }
426}
427
428#[derive(Debug, Default, FromAttributes)]
429#[darling(default, attributes(configurable))]
430struct Attributes {
431    #[darling(default)]
432    title: Option<String>,
433    #[darling(default)]
434    description: Option<String>,
435    #[darling(default)]
436    deprecated: Flag,
437    #[darling(multiple)]
438    metadata: Vec<Metadata>,
439}
440
441impl Attributes {
442    fn finalize(mut self, forwarded_attrs: &[syn::Attribute]) -> darling::Result<Self> {
443        // We additionally attempt to extract a title/description from the forwarded doc attributes, if they exist.
444        // Whether we extract both a title and description, or just description, is documented in more detail in
445        // `try_extract_doc_title_description` itself.
446        let (doc_title, doc_description) = try_extract_doc_title_description(forwarded_attrs);
447        self.title = self.title.or(doc_title);
448        self.description = self.description.or(doc_description);
449
450        Ok(self)
451    }
452}
453
454/// Gets the idents for a type that potentially represent generic type parameters.
455///
456/// We use this function to take the `Type` of a field, and figure out if it has any generic type
457/// parameters, such as the `T` in `Vec<T>`. As the type itself might be the generic parameter (just
458/// a plain `T`) we potentially return the ident of the type itself unless we can determine that the
459/// type path has generic type arguments.
460fn get_generic_type_param_idents(ty: &Type) -> Option<Vec<Ident>> {
461    match ty {
462        Type::Path(tp) => match tp.path.segments.len() {
463            0 => unreachable!(
464                "A type path with no path segments should not be possible to construct normally."
465            ),
466            // A single path segment would be something like `String` or `Vec<T>`, so we
467            // do need to check for both scenarios.
468            1 => match tp.path.segments.first() {
469                None => unreachable!("Can only reach match arm if segment length was 1."),
470                Some(segment) => get_generic_args_from_path_segment(segment, true),
471            },
472            _ => {
473                let idents = tp
474                    .path
475                    .segments
476                    .iter()
477                    .filter_map(|segment| get_generic_args_from_path_segment(segment, false))
478                    .flatten()
479                    .collect::<Vec<_>>();
480
481                if idents.is_empty() {
482                    None
483                } else {
484                    Some(idents)
485                }
486            }
487        },
488        _ => None,
489    }
490}
491
492fn get_generic_args_from_path_segment(
493    segment: &PathSegment,
494    return_self: bool,
495) -> Option<Vec<Ident>> {
496    match &segment.arguments {
497        // If the segment has no brackets/parens, return its ident as-is if we should return self.
498        // When we're trying to parse a higher-level type path that has multiple segments, we
499        // wouldn't want to return the segment's ident, because if we were parsing
500        // `std::vec::Vec<T>`, that would lead to us returning `std`, `vec`, and `T`... the first
501        // two of which would make no sense, obviously.
502        PathArguments::None => {
503            if return_self {
504                Some(vec![segment.ident.clone()])
505            } else {
506                None
507            }
508        }
509        PathArguments::AngleBracketed(angle_args) => {
510            let args = angle_args
511                .args
512                .iter()
513                .filter_map(|generic| match generic {
514                    // We only care about generic type arguments.
515                    GenericArgument::Type(gty) => get_generic_type_path_ident(gty),
516                    _ => None,
517                })
518                .collect::<Vec<_>>();
519
520            if args.is_empty() {
521                None
522            } else {
523                Some(args)
524            }
525        }
526        // We don't support parenthesized generic arguments as they only come up in the case of
527        // function pointers, and we don't support those with `Configurable`.
528        PathArguments::Parenthesized(_) => None,
529    }
530}
531
532/// Gets the ident of a `Type` when it is a "path" type.
533///
534/// Path types look like `String` or `std::vec::Vec<T>`, and represent a type you could accept as a
535/// generic type argument.
536fn get_generic_type_path_ident(ty: &Type) -> Option<Ident> {
537    match ty {
538        Type::Path(tp) => tp.path.get_ident().cloned(),
539        _ => None,
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use proc_macro2::Ident;
546    use quote::format_ident;
547    use syn::{parse_quote, Type};
548
549    use super::get_generic_type_param_idents;
550
551    fn literals_to_idents(idents: &[&str]) -> Vec<Ident> {
552        idents.iter().map(|raw| format_ident!("{}", raw)).collect()
553    }
554
555    #[test]
556    fn test_get_generic_type_param_idents() {
557        // A "direct" type reference, like a type that's already in scope, is a single ident so we
558        // do want to capture that.
559        let direct_concrete_type: Type = parse_quote! { String };
560        let idents = get_generic_type_param_idents(&direct_concrete_type)
561            .expect("idents should have been found");
562        assert_eq!(literals_to_idents(&["String"]), idents);
563
564        // Segmented type paths like this can't get represented as idents, which is also why they
565        // can't possibly represent a generic type parameter, as a generic type parameter is always
566        // a single ident, i.e. `T`.
567        let qualified_concrete_type: Type = parse_quote! { std::string::String };
568        let idents = get_generic_type_param_idents(&qualified_concrete_type);
569        assert_eq!(None, idents);
570
571        // This one is pretty obvious.
572        let direct_generic_type: Type = parse_quote! { T };
573        let idents = get_generic_type_param_idents(&direct_generic_type)
574            .expect("idents should have been found");
575        assert_eq!(literals_to_idents(&["T"]), idents);
576
577        // We should always extract the generic type parameter, even for a "direct" type reference.
578        let contained_generic_type: Type = parse_quote! { Vec<T> };
579        let idents = get_generic_type_param_idents(&contained_generic_type)
580            .expect("idents should have been found");
581        assert_eq!(literals_to_idents(&["T"]), idents);
582
583        // Similarly, we should always extract the generic type parameter for segmented type paths,
584        // since we traverse all segments.
585        let qualified_contained_generic_type: Type = parse_quote! { std::vec::Vec<T> };
586        let idents = get_generic_type_param_idents(&qualified_contained_generic_type)
587            .expect("idents should have been found");
588        assert_eq!(literals_to_idents(&["T"]), idents);
589
590        // We don't support parenthesized type parameters, like when using a function pointer type.
591        let parenthesized_type: Type = parse_quote! { Something<fn(bool) -> String> };
592        let idents = get_generic_type_param_idents(&parenthesized_type);
593        assert_eq!(None, idents);
594    }
595}