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}