vector_config_macros/ast/variant.rs
1use darling::{FromAttributes, error::Accumulator, util::Flag};
2use proc_macro2::{Ident, TokenStream};
3use quote::ToTokens;
4use serde_derive_internals::ast as serde_ast;
5
6use super::{
7 Field, LazyCustomAttribute, Metadata, Style, Tagging,
8 util::{DarlingResultIterator, has_flag_attribute, try_extract_doc_title_description},
9};
10
11/// A variant in an enum.
12pub struct Variant<'a> {
13 original: &'a syn::Variant,
14 name: String,
15 attrs: Attributes,
16 fields: Vec<Field<'a>>,
17 style: Style,
18 tagging: Tagging,
19}
20
21impl<'a> Variant<'a> {
22 /// Creates a new `Variant<'a>` from the `serde`-derived information about the given variant.
23 pub fn from_ast(
24 serde: &serde_ast::Variant<'a>,
25 tagging: Tagging,
26 is_virtual_newtype: bool,
27 ) -> darling::Result<Variant<'a>> {
28 let original = serde.original;
29 let name = serde.attrs.name().deserialize_name().to_string();
30 let style = serde.style.into();
31 let is_newtype_wrapper_field = style == Style::Newtype;
32
33 let attrs = Attributes::from_attributes(&original.attrs)
34 .and_then(|attrs| attrs.finalize(serde, &original.attrs))?;
35
36 let mut accumulator = Accumulator::default();
37 let fields = serde
38 .fields
39 .iter()
40 .map(|field| Field::from_ast(field, is_virtual_newtype, is_newtype_wrapper_field))
41 .collect_darling_results(&mut accumulator);
42
43 // If the enum overall is tagged (internal/adjacent) serde still allows one or more
44 // variants to be explicitly marked with `#[serde(untagged)]`. In that case, the
45 // variant itself is untagged; reflect that here so schema generation matches serde.
46 let tagging = if has_flag_attribute(&original.attrs, "serde", "untagged") {
47 Tagging::None
48 } else {
49 tagging
50 };
51
52 let variant = Variant {
53 original,
54 name,
55 attrs,
56 fields,
57 style,
58 tagging,
59 };
60 accumulator.finish_with(variant)
61 }
62
63 /// Ident of the variant.
64 pub fn ident(&self) -> &Ident {
65 &self.original.ident
66 }
67
68 /// Style of the variant.
69 ///
70 /// This comes directly from `serde`, but effectively represents common terminology used outside
71 /// of `serde` when describing the shape of a data container, such as if a struct is a "tuple
72 /// struct" or a "newtype wrapper", and so on.
73 pub fn style(&self) -> Style {
74 self.style
75 }
76
77 /// Tagging configuration of the variant.
78 ///
79 /// This comes directly from `serde`. For more information on tagging, see [Enum representations][serde_tagging_docs].
80 ///
81 /// [serde_tagging_docs]: https://serde.rs/enum-representations.html
82 pub fn tagging(&self) -> &Tagging {
83 &self.tagging
84 }
85
86 /// Fields of the variant, if any.
87 pub fn fields(&self) -> &[Field<'_>] {
88 &self.fields
89 }
90
91 /// Name of the variant when deserializing.
92 ///
93 /// This may be different than the name of the variant itself depending on whether it has been
94 /// altered with `serde` helper attributes i.e. `#[serde(rename = "...")]`.
95 pub fn name(&self) -> &str {
96 self.name.as_str()
97 }
98
99 /// Title of the variant, if any.
100 ///
101 /// The title specifically refers to the headline portion of a doc comment. For example, if a
102 /// variant has the following doc comment:
103 ///
104 /// ```text
105 /// /// My special variant.
106 /// ///
107 /// /// Here's why it's special:
108 /// /// ...
109 /// SomeVariant(...),
110 /// ```
111 ///
112 /// then the title would be `My special variant`. If the doc comment only contained `My special
113 /// variant.`, then we would consider the title _empty_. See `description` for more details on
114 /// detecting titles vs descriptions.
115 pub fn title(&self) -> Option<&String> {
116 self.attrs.title.as_ref()
117 }
118
119 /// Description of the variant, if any.
120 ///
121 /// The description specifically refers to the body portion of a doc comment, or the headline if
122 /// only a headline exists.. For example, if a variant has the following doc comment:
123 ///
124 /// ```text
125 /// /// My special variant.
126 /// ///
127 /// /// Here's why it's special:
128 /// /// ...
129 /// SomeVariant(...),
130 /// ```
131 ///
132 /// then the title would be everything that comes after `My special variant`. If the doc comment
133 /// only contained `My special variant.`, then the description would be `My special variant.`,
134 /// and the title would be empty. In this way, the description will always be some port of a
135 /// doc comment, depending on the formatting applied.
136 ///
137 /// This logic was chosen to mimic how Rust's own `rustdoc` tool works, where it will use the
138 /// "title" portion as a high-level description for an item, only showing the title and
139 /// description together when drilling down to the documentation for that specific item. JSON
140 /// Schema supports both title and description for a schema, and so we expose both.
141 pub fn description(&self) -> Option<&String> {
142 self.attrs.description.as_ref()
143 }
144
145 /// Whether or not the variant is deprecated.
146 ///
147 /// Applying the `#[configurable(deprecated)]` helper attribute will mark this variant as
148 /// deprecated from the perspective of the resulting schema. It does not interact with Rust's
149 /// standard `#[deprecated]` attribute, neither automatically applying it nor deriving the
150 /// deprecation status of a variant when it is present.
151 pub fn deprecated(&self) -> bool {
152 self.attrs.deprecated.is_present()
153 }
154
155 /// Whether or not this variant is visible during either serialization or deserialization.
156 ///
157 /// This is derived from whether any of the `serde` visibility attributes are applied: `skip`,
158 /// `skip_serializing`, and `skip_deserializing`. Unless the variant is skipped entirely, it will
159 /// be considered visible and part of the schema.
160 pub fn visible(&self) -> bool {
161 self.attrs.visible
162 }
163
164 /// Metadata (custom attributes) for the variant, if any.
165 ///
166 /// Attributes can take the shape of flags (`#[configurable(metadata(im_a_teapot))]`) or
167 /// key/value pairs (`#[configurable(metadata(status = "beta"))]`) to allow rich, semantic
168 /// metadata to be attached directly to variants.
169 pub fn metadata(&self) -> impl Iterator<Item = LazyCustomAttribute> {
170 self.attrs
171 .metadata
172 .clone()
173 .into_iter()
174 .flat_map(|metadata| metadata.attributes())
175 }
176}
177
178impl ToTokens for Variant<'_> {
179 fn to_tokens(&self, tokens: &mut TokenStream) {
180 self.original.to_tokens(tokens)
181 }
182}
183
184#[derive(Debug, Default, FromAttributes)]
185#[darling(default, attributes(configurable))]
186struct Attributes {
187 title: Option<String>,
188 description: Option<String>,
189 deprecated: Flag,
190 #[darling(skip)]
191 visible: bool,
192 #[darling(multiple)]
193 metadata: Vec<Metadata>,
194}
195
196impl Attributes {
197 fn finalize(
198 mut self,
199 variant: &serde_ast::Variant<'_>,
200 forwarded_attrs: &[syn::Attribute],
201 ) -> darling::Result<Self> {
202 // Derive any of the necessary fields from the `serde` side of things.
203 self.visible = !variant.attrs.skip_deserializing() || !variant.attrs.skip_serializing();
204
205 // We additionally attempt to extract a title/description from the forwarded doc attributes, if they exist.
206 // Whether we extract both a title and description, or just description, is documented in more detail in
207 // `try_extract_doc_title_description` itself.
208 let (doc_title, doc_description) = try_extract_doc_title_description(forwarded_attrs);
209 self.title = self.title.or(doc_title);
210 self.description = self.description.or(doc_description);
211
212 Ok(self)
213 }
214}
215
216impl PartialEq for Variant<'_> {
217 fn eq(&self, other: &Self) -> bool {
218 // Equality checking between variants is only used to drive conformance checks around making
219 // sure no duplicate variants exist when in untagged mode, so all we care about is what
220 // distinguishes a variant when it's in its serialized form, which is the shape -- struct vs
221 // tuple vs unit -- and the fields therein.
222
223 // It's suboptimal to be allocating strings for the field names here but we need the
224 // deserialized name as `serde` observes it, and this only runs at compile-time.
225 let self_fields = self
226 .fields
227 .iter()
228 .map(|field| (field.name(), field.ty()))
229 .collect::<Vec<_>>();
230 let other_fields = other
231 .fields
232 .iter()
233 .map(|field| (field.name(), field.ty()))
234 .collect::<Vec<_>>();
235
236 self.style() == other.style()
237 && self.tagging == other.tagging
238 && self_fields == other_fields
239 }
240}