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