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::{FromAttributes, error::Accumulator, util::Flag};
8use serde_derive_internals::{Ctxt, Derive, ast as serde_ast};
9use syn::{
10    DeriveInput, ExprPath, GenericArgument, Generics, Ident, PathArguments, PathSegment, Type,
11    TypeParam,
12};
13
14use super::{
15    Data, Field, LazyCustomAttribute, Metadata, Style, Tagging, Variant,
16    util::{
17        DarlingResultIterator, err_serde_failed, get_serde_default_value,
18        try_extract_doc_title_description,
19    },
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()
179                                && tagging != Tagging::None
180                                && variant.tagging() != &Tagging::None
181                            {
182                                accumulator.push(
183                                    darling::Error::custom(ERR_NO_ENUM_VARIANT_DESCRIPTION)
184                                        .with_span(variant),
185                                );
186                            }
187
188                            // Serde allows multiple untagged variants in a tagged enum. We do not
189                            // restrict their count or order here; ambiguity handling is done via
190                            // schema generation strategy (e.g., falling back to `anyOf`) and
191                            // discriminant hints when needed.
192                        }
193
194                        // If we're in untagged mode, there can be no duplicate variants.
195                        if tagging == Tagging::None {
196                            for (i, variant) in variants.iter().enumerate() {
197                                for (k, other_variant) in variants.iter().enumerate() {
198                                    if variant == other_variant && i != k {
199                                        accumulator.push(
200                                            darling::Error::custom(ERR_ENUM_UNTAGGED_DUPLICATES)
201                                                .with_span(variant),
202                                        );
203                                    }
204                                }
205                            }
206                        }
207
208                        (Data::Enum(variants), true)
209                    }
210                    serde_ast::Data::Struct(style, fields) => match style {
211                        serde_ast::Style::Struct
212                        | serde_ast::Style::Tuple
213                        | serde_ast::Style::Newtype => {
214                            let is_newtype_wrapper_field =
215                                matches!(style, serde_ast::Style::Newtype);
216                            let fields = fields
217                                .iter()
218                                .map(|field| {
219                                    Field::from_ast(
220                                        field,
221                                        virtual_newtype.is_some(),
222                                        is_newtype_wrapper_field,
223                                    )
224                                })
225                                .collect_darling_results(&mut accumulator);
226
227                            (Data::Struct(style.into(), fields), false)
228                        }
229                        serde_ast::Style::Unit => {
230                            // This is a little ugly but we can't drop the accumulator without finishing it, otherwise
231                            // it will panic to let us know we didn't assert whether there were errors or not... so add
232                            // our error and just return a dummy value.
233                            accumulator
234                                .push(darling::Error::custom(ERR_NO_UNIT_STRUCTS).with_span(input));
235                            (Data::Struct(Style::Unit, Vec::new()), false)
236                        }
237                    },
238                };
239
240                // All containers must have a description: no ifs, ands, or buts.
241                //
242                // The compile-time errors are a bit too inscrutable otherwise, and inscrutable errors are not very
243                // helpful when using procedural macros.
244                if attrs.description.is_none() {
245                    accumulator
246                        .push(darling::Error::custom(ERR_MISSING_DESC).with_span(&serde.ident));
247                }
248
249                let original = input;
250                let name = serde.attrs.name().deserialize_name().to_string();
251                let default_value = get_serde_default_value(&serde.ident, serde.attrs.default());
252
253                let container = Container {
254                    original,
255                    name,
256                    default_value,
257                    data,
258                    virtual_newtype,
259                    tagging: is_enum.then_some(tagging),
260                    attrs,
261                };
262
263                accumulator.finish_with(container)
264            })
265    }
266
267    /// Ident of the container.
268    ///
269    /// This is simply the name or type of a struct/enum, but is not parsed directly as a type via
270    /// `syn`, only an `Ident`.
271    pub fn ident(&self) -> &Ident {
272        &self.original.ident
273    }
274
275    /// Generics for the container, if any.
276    pub fn generics(&self) -> &Generics {
277        &self.original.generics
278    }
279
280    /// Data for the container.
281    ///
282    /// This would be the fields of a struct, or the variants for an enum.
283    pub fn data(&self) -> &Data<'_> {
284        &self.data
285    }
286
287    /// Name of the container when deserializing.
288    ///
289    /// This may be different than the name of the container itself depending on whether it has been
290    /// altered with `serde` helper attributes i.e. `#[serde(rename = "...")]`.
291    pub fn name(&self) -> &str {
292        self.name.as_str()
293    }
294
295    /// Title of the container, if any.
296    ///
297    /// The title specifically refers to the headline portion of a doc comment. For example, if a
298    /// struct has the following doc comment:
299    ///
300    /// ```text
301    /// /// My special struct.
302    /// ///
303    /// /// Here's why it's special:
304    /// /// ...
305    /// struct Wrapper(u64);
306    /// ```
307    ///
308    /// then the title would be `My special struct`. If the doc comment only contained `My special
309    /// struct.`, then we would consider the title _empty_. See `description` for more details on
310    /// detecting titles vs descriptions.
311    pub fn title(&self) -> Option<&String> {
312        self.attrs.title.as_ref()
313    }
314
315    /// Description of the struct, if any.
316    ///
317    /// The description specifically refers to the body portion of a doc comment, or the headline if
318    /// only a headline exists.. For example, if a struct has the following doc comment:
319    ///
320    /// ```text
321    /// /// My special struct.
322    /// ///
323    /// /// Here's why it's special:
324    /// /// ...
325    /// struct Wrapper(u64);
326    /// ```
327    ///
328    /// then the title would be everything that comes after `My special struct`. If the doc comment
329    /// only contained `My special struct.`, then the description would be `My special struct.`, and
330    /// the title would be empty. In this way, the description will always be some port of a doc
331    /// comment, depending on the formatting applied.
332    ///
333    /// This logic was chosen to mimic how Rust's own `rustdoc` tool works, where it will use the
334    /// "title" portion as a high-level description for an item, only showing the title and
335    /// description together when drilling down to the documentation for that specific item. JSON
336    /// Schema supports both title and description for a schema, and so we expose both.
337    pub fn description(&self) -> Option<&String> {
338        self.attrs.description.as_ref()
339    }
340
341    /// Virtual type of this container, if any.
342    ///
343    /// In some cases, a type may be representable by an entirely different type, and then converted
344    /// to the desired type using the common `TryFrom`/`From`/`Into` conversion traits. This is a
345    /// common pattern with `serde` to re-use existing conversion logic (such as taking a raw string
346    /// and parsing it to see if it's a valid regular expression, and so on) but represents a
347    /// divergence between the type we're generating a schema for and the actual type that will be
348    /// getting (de)serialized.
349    ///
350    /// When we detect a container with the right `serde` helper attributes (see the code in
351    /// `from_derive_input` for details), we switch to treating this container as, for the purpose
352    /// of schema generation, having the type specified by those helper attributes.
353    pub fn virtual_newtype(&self) -> Option<Type> {
354        self.virtual_newtype.clone()
355    }
356
357    /// Tagging mode of this container.
358    ///
359    /// When the container is an enum, `Some(..)` will be returned, where the value can be any of
360    /// the four modes supported by `serde`: external, internal, adjacent, or none (untagged).
361    ///
362    /// When the container is a struct, `None` is returned.
363    pub fn tagging(&self) -> Option<&Tagging> {
364        self.tagging.as_ref()
365    }
366
367    /// Path to a function to call to generate a default value for the container, if any.
368    ///
369    /// This will boil down to something like `std::default::Default::default` or
370    /// `name_of_in_scope_method_to_call`, where we generate code to actually call that path as a
371    /// function to generate the default value we include in the schema for this container.
372    pub fn default_value(&self) -> Option<ExprPath> {
373        self.default_value.clone()
374    }
375
376    /// Whether or not the container is deprecated.
377    ///
378    /// Applying the `#[configurable(deprecated)]` helper attribute will mark this container as
379    /// deprecated from the perspective of the resulting schema. It does not interact with Rust's
380    /// standard `#[deprecated]` attribute, neither automatically applying it nor deriving the
381    /// deprecation status of a field when it is present.
382    pub fn deprecated(&self) -> bool {
383        self.attrs.deprecated.is_present()
384    }
385
386    /// Metadata (custom attributes) for the container, if any.
387    ///
388    /// Attributes can take the shape of flags (`#[configurable(metadata(im_a_teapot))]`) or
389    /// key/value pairs (`#[configurable(metadata(status = "beta"))]`) to allow rich, semantic
390    /// metadata to be attached directly to containers.
391    pub fn metadata(&self) -> impl Iterator<Item = LazyCustomAttribute> {
392        self.attrs
393            .metadata
394            .clone()
395            .into_iter()
396            .flat_map(|metadata| metadata.attributes())
397    }
398
399    /// Gets the generic types that are used within fields or variants that are part of the schema.
400    ///
401    /// In order to ensure we can allow for a maximally flexible `Configurable` trait, we add bounds to generic types that are
402    /// present on derived containers so that bounds don't need to be added on the actual container itself, essentially
403    /// avoiding declarations like `pub struct Foo<T> where T: Configurable {...}`.
404    ///
405    /// We contain this logic here as we only care about generic type parameters that are present on fields that will be
406    /// included in the schema, so skipped fields shouldn't have bounds added, and so on.
407    pub fn generic_field_types(&self) -> Vec<TypeParam> {
408        let mut generic_types = Vec::new();
409
410        let field_types = match &self.data {
411            Data::Struct(_, fields) => fields
412                .iter()
413                .filter(|f| f.visible())
414                .filter_map(|f| get_generic_type_param_idents(f.ty()))
415                .flatten()
416                .collect::<HashSet<_>>(),
417            Data::Enum(variants) => variants
418                .iter()
419                .filter(|v| v.visible())
420                .flat_map(|v| v.fields().iter())
421                .filter_map(|f| get_generic_type_param_idents(f.ty()))
422                .flatten()
423                .collect::<HashSet<_>>(),
424        };
425
426        for type_param in self.original.generics.type_params() {
427            if field_types.contains(&type_param.ident) {
428                generic_types.push(type_param.clone());
429            }
430        }
431
432        generic_types
433    }
434}
435
436#[derive(Debug, Default, FromAttributes)]
437#[darling(default, attributes(configurable))]
438struct Attributes {
439    #[darling(default)]
440    title: Option<String>,
441    #[darling(default)]
442    description: Option<String>,
443    #[darling(default)]
444    deprecated: Flag,
445    #[darling(multiple)]
446    metadata: Vec<Metadata>,
447}
448
449impl Attributes {
450    fn finalize(mut self, forwarded_attrs: &[syn::Attribute]) -> darling::Result<Self> {
451        // We additionally attempt to extract a title/description from the forwarded doc attributes, if they exist.
452        // Whether we extract both a title and description, or just description, is documented in more detail in
453        // `try_extract_doc_title_description` itself.
454        let (doc_title, doc_description) = try_extract_doc_title_description(forwarded_attrs);
455        self.title = self.title.or(doc_title);
456        self.description = self.description.or(doc_description);
457
458        Ok(self)
459    }
460}
461
462/// Gets the idents for a type that potentially represent generic type parameters.
463///
464/// We use this function to take the `Type` of a field, and figure out if it has any generic type
465/// parameters, such as the `T` in `Vec<T>`. As the type itself might be the generic parameter (just
466/// a plain `T`) we potentially return the ident of the type itself unless we can determine that the
467/// type path has generic type arguments.
468fn get_generic_type_param_idents(ty: &Type) -> Option<Vec<Ident>> {
469    match ty {
470        Type::Path(tp) => match tp.path.segments.len() {
471            0 => unreachable!(
472                "A type path with no path segments should not be possible to construct normally."
473            ),
474            // A single path segment would be something like `String` or `Vec<T>`, so we
475            // do need to check for both scenarios.
476            1 => match tp.path.segments.first() {
477                None => unreachable!("Can only reach match arm if segment length was 1."),
478                Some(segment) => get_generic_args_from_path_segment(segment, true),
479            },
480            _ => {
481                let idents = tp
482                    .path
483                    .segments
484                    .iter()
485                    .filter_map(|segment| get_generic_args_from_path_segment(segment, false))
486                    .flatten()
487                    .collect::<Vec<_>>();
488
489                if idents.is_empty() {
490                    None
491                } else {
492                    Some(idents)
493                }
494            }
495        },
496        _ => None,
497    }
498}
499
500fn get_generic_args_from_path_segment(
501    segment: &PathSegment,
502    return_self: bool,
503) -> Option<Vec<Ident>> {
504    match &segment.arguments {
505        // If the segment has no brackets/parens, return its ident as-is if we should return self.
506        // When we're trying to parse a higher-level type path that has multiple segments, we
507        // wouldn't want to return the segment's ident, because if we were parsing
508        // `std::vec::Vec<T>`, that would lead to us returning `std`, `vec`, and `T`... the first
509        // two of which would make no sense, obviously.
510        PathArguments::None => {
511            if return_self {
512                Some(vec![segment.ident.clone()])
513            } else {
514                None
515            }
516        }
517        PathArguments::AngleBracketed(angle_args) => {
518            let args = angle_args
519                .args
520                .iter()
521                .filter_map(|generic| match generic {
522                    // We only care about generic type arguments.
523                    GenericArgument::Type(gty) => get_generic_type_path_ident(gty),
524                    _ => None,
525                })
526                .collect::<Vec<_>>();
527
528            if args.is_empty() { None } else { Some(args) }
529        }
530        // We don't support parenthesized generic arguments as they only come up in the case of
531        // function pointers, and we don't support those with `Configurable`.
532        PathArguments::Parenthesized(_) => None,
533    }
534}
535
536/// Gets the ident of a `Type` when it is a "path" type.
537///
538/// Path types look like `String` or `std::vec::Vec<T>`, and represent a type you could accept as a
539/// generic type argument.
540fn get_generic_type_path_ident(ty: &Type) -> Option<Ident> {
541    match ty {
542        Type::Path(tp) => tp.path.get_ident().cloned(),
543        _ => None,
544    }
545}
546
547#[cfg(test)]
548mod tests {
549    use proc_macro2::Ident;
550    use quote::format_ident;
551    use syn::{Type, parse_quote};
552
553    use super::get_generic_type_param_idents;
554
555    fn literals_to_idents(idents: &[&str]) -> Vec<Ident> {
556        idents.iter().map(|raw| format_ident!("{}", raw)).collect()
557    }
558
559    #[test]
560    fn test_get_generic_type_param_idents() {
561        // A "direct" type reference, like a type that's already in scope, is a single ident so we
562        // do want to capture that.
563        let direct_concrete_type: Type = parse_quote! { String };
564        let idents = get_generic_type_param_idents(&direct_concrete_type)
565            .expect("idents should have been found");
566        assert_eq!(literals_to_idents(&["String"]), idents);
567
568        // Segmented type paths like this can't get represented as idents, which is also why they
569        // can't possibly represent a generic type parameter, as a generic type parameter is always
570        // a single ident, i.e. `T`.
571        let qualified_concrete_type: Type = parse_quote! { std::string::String };
572        let idents = get_generic_type_param_idents(&qualified_concrete_type);
573        assert_eq!(None, idents);
574
575        // This one is pretty obvious.
576        let direct_generic_type: Type = parse_quote! { T };
577        let idents = get_generic_type_param_idents(&direct_generic_type)
578            .expect("idents should have been found");
579        assert_eq!(literals_to_idents(&["T"]), idents);
580
581        // We should always extract the generic type parameter, even for a "direct" type reference.
582        let contained_generic_type: Type = parse_quote! { Vec<T> };
583        let idents = get_generic_type_param_idents(&contained_generic_type)
584            .expect("idents should have been found");
585        assert_eq!(literals_to_idents(&["T"]), idents);
586
587        // Similarly, we should always extract the generic type parameter for segmented type paths,
588        // since we traverse all segments.
589        let qualified_contained_generic_type: Type = parse_quote! { std::vec::Vec<T> };
590        let idents = get_generic_type_param_idents(&qualified_contained_generic_type)
591            .expect("idents should have been found");
592        assert_eq!(literals_to_idents(&["T"]), idents);
593
594        // We don't support parenthesized type parameters, like when using a function pointer type.
595        let parenthesized_type: Type = parse_quote! { Something<fn(bool) -> String> };
596        let idents = get_generic_type_param_idents(&parenthesized_type);
597        assert_eq!(None, idents);
598    }
599}