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}