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