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}