1use super::{
2 structs::{Field, Struct, StructOptions},
3 Type, TypeIdent,
4};
5use crate::types::format_bounds;
6use crate::{casing::Casing, docs::get_doc_lines, primitives::Primitive, types::FieldAttrs};
7use quote::ToTokens;
8use std::{convert::TryFrom, str::FromStr};
9use syn::{
10 ext::IdentExt, parenthesized, parse::Parse, parse::ParseStream, Attribute, Error, GenericParam,
11 Ident, ItemEnum, LitStr, Result, Token, TypePath,
12};
13
14#[derive(Clone, Debug, Eq, Hash, PartialEq)]
15pub struct Enum {
16 pub ident: TypeIdent,
17 pub variants: Vec<Variant>,
18 pub doc_lines: Vec<String>,
19 pub options: EnumOptions,
20}
21
22pub(crate) fn parse_enum_item(item: ItemEnum) -> Enum {
23 let ident = TypeIdent {
24 name: item.ident.to_string(),
25 generic_args: item
26 .generics
27 .params
28 .iter()
29 .filter_map(|param| match param {
30 GenericParam::Type(ty) => {
31 Some((TypeIdent::from(ty.ident.to_string()), format_bounds(ty)))
32 }
33 _ => None,
34 })
35 .collect(),
36 ..Default::default()
37 };
38 let options = EnumOptions::from_attrs(&item.attrs);
39 let variants = item
40 .variants
41 .iter()
42 .map(|variant| {
43 if variant.discriminant.is_some() {
44 panic!(
45 "Discriminants in enum variants are not supported. Found: {:?}",
46 item
47 );
48 }
49
50 let has_inline_tag =
52 options.tag_prop_name.is_some() && options.content_prop_name.is_none();
53
54 let name = variant.ident.to_string();
55 let ty = if variant.fields.is_empty() {
56 Type::Unit
57 } else if variant.fields.iter().any(|field| field.ident.is_some()) {
58 let fields = variant
59 .fields
60 .iter()
61 .map(|field| {
62 let name = field
63 .ident
64 .as_ref()
65 .unwrap_or_else(|| panic!("Unnamed field in variant of enum {}", ident))
66 .to_string();
67 if has_inline_tag && options.tag_prop_name.as_ref() == Some(&name) {
68 panic!(
69 "Enum {} cannot be serialized, because the variant `{}` has a \
70 field with the same name as the enum's `tag` attribute",
71 ident, variant.ident
72 );
73 }
74
75 Field {
76 name: Some(name),
77 ty: TypeIdent::try_from(&field.ty)
78 .unwrap_or_else(|_| panic!("Invalid field type in enum {}", ident)),
79 doc_lines: get_doc_lines(&field.attrs),
80 attrs: FieldAttrs::from_attrs(&field.attrs),
81 }
82 })
83 .collect();
84 Type::Struct(Struct {
85 ident: TypeIdent::from(name.clone()),
86 fields,
87 doc_lines: Vec::new(),
88 options: StructOptions::default(),
89 })
90 } else {
91 let item_types: Vec<_> = variant
92 .fields
93 .iter()
94 .map(|field| {
95 if has_inline_tag && is_path_to_primitive(&field.ty) {
96 panic!(
97 "Enum {} cannot be serialized, because the variant `{}` has a \
98 primitive unnamed field ({}) and the enum has no `content` \
99 attribute",
100 ident,
101 variant.ident,
102 field.ty.to_token_stream()
103 );
104 }
105
106 TypeIdent::try_from(&field.ty)
107 .unwrap_or_else(|_| panic!("Invalid field type in enum {}", ident))
108 })
109 .collect();
110
111 if has_inline_tag && item_types.len() > 1 {
112 panic!(
113 "Enum {} cannot be serialized, because the variant `{}` contains multiple \
114 unnamed fields and the enum has no `content` attribute",
115 ident, variant.ident,
116 );
117 }
118
119 Type::Tuple(item_types)
120 };
121 let doc_lines = get_doc_lines(&variant.attrs);
122 let attrs = VariantAttrs::from_attrs(&variant.attrs);
123
124 Variant {
125 name,
126 ty,
127 doc_lines,
128 attrs,
129 }
130 })
131 .collect();
132
133 Enum {
134 ident,
135 variants,
136 doc_lines: get_doc_lines(&item.attrs),
137 options,
138 }
139}
140
141#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
142pub struct EnumOptions {
143 pub variant_casing: Casing,
144 pub content_prop_name: Option<String>,
145 pub tag_prop_name: Option<String>,
146
147 pub untagged: bool,
151
152 pub rust_module: Option<String>,
172}
173
174impl EnumOptions {
175 pub fn from_attrs(attrs: &[Attribute]) -> Self {
176 let mut opts = Self::default();
177 for attr in attrs {
178 if attr.path.is_ident("fp") || attr.path.is_ident("serde") {
179 opts.merge_with(
180 &syn::parse2::<Self>(attr.tokens.clone()).expect("Could not parse attributes"),
181 );
182 }
183 }
184 opts
185 }
186
187 fn merge_with(&mut self, other: &EnumOptions) {
188 if other.variant_casing != Casing::default() {
189 self.variant_casing = other.variant_casing;
190 }
191 if other.content_prop_name.is_some() {
192 self.content_prop_name = other.content_prop_name.clone();
193 }
194 if other.tag_prop_name.is_some() {
195 self.tag_prop_name = other.tag_prop_name.clone();
196 }
197 if other.untagged {
198 self.untagged = true;
199 }
200 if let Some(other_rust_module) = &other.rust_module {
201 self.rust_module = Some(other_rust_module.clone());
202 }
203 }
204
205 pub fn to_serde_attrs(&self) -> Vec<String> {
206 let mut serde_attrs = vec![];
207 if self.untagged {
208 serde_attrs.push("untagged".to_owned());
209 } else if let Some(prop_name) = &self.tag_prop_name {
210 serde_attrs.push(format!("tag = \"{prop_name}\""));
211
212 if let Some(prop_name) = &self.content_prop_name {
213 serde_attrs.push(format!("content = \"{prop_name}\""));
214 }
215 }
216 if let Some(casing) = &self.variant_casing.as_maybe_str() {
217 serde_attrs.push(format!("rename_all = \"{casing}\""));
218 }
219 serde_attrs
220 }
221}
222
223impl Parse for EnumOptions {
224 fn parse(input: ParseStream) -> Result<Self> {
225 let content;
226 parenthesized!(content in input);
227
228 let parse_value = || -> Result<String> {
229 content.parse::<Token![=]>()?;
230 Ok(content
231 .parse::<LitStr>()?
232 .to_token_stream()
233 .to_string()
234 .trim_matches('"')
235 .to_owned())
236 };
237
238 let mut result = Self::default();
239 loop {
240 let key: Ident = content.call(IdentExt::parse_any)?;
241 match key.to_string().as_ref() {
242 "content" => result.content_prop_name = Some(parse_value()?),
243 "tag" => result.tag_prop_name = Some(parse_value()?),
244 "rename_all" => {
245 result.variant_casing = Casing::try_from(parse_value()?.as_ref())
246 .map_err(|err| Error::new(content.span(), err))?
247 }
248 "rust_module" => {
249 result.rust_module = Some(parse_value()?);
250 }
251 "untagged" => result.untagged = true,
252 other => {
253 return Err(Error::new(
254 content.span(),
255 format!("Unexpected attribute: {other}"),
256 ))
257 }
258 }
259
260 if content.is_empty() {
261 break;
262 }
263
264 content.parse::<Token![,]>()?;
265 }
266
267 Ok(result)
268 }
269}
270
271#[derive(Clone, Debug, Eq, Hash, PartialEq)]
272pub struct Variant {
273 pub name: String,
274 pub ty: Type,
275 pub doc_lines: Vec<String>,
276 pub attrs: VariantAttrs,
277}
278
279#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
280pub struct VariantAttrs {
281 pub field_casing: Casing,
282
283 pub rename: Option<String>,
288}
289
290impl VariantAttrs {
291 pub fn from_attrs(attrs: &[Attribute]) -> Self {
292 let mut opts = Self::default();
293 for attr in attrs {
294 if attr.path.is_ident("fp") || attr.path.is_ident("serde") {
295 opts.merge_with(
296 &syn::parse2::<Self>(attr.tokens.clone())
297 .expect("Could not parse variant attributes"),
298 );
299 }
300 }
301 opts
302 }
303
304 fn merge_with(&mut self, other: &Self) {
305 if other.field_casing != Casing::default() {
306 self.field_casing = other.field_casing;
307 }
308 if other.rename.is_some() {
309 self.rename = other.rename.clone();
310 }
311 }
312
313 pub fn to_serde_attrs(&self) -> Vec<String> {
314 let mut serde_attrs = vec![];
315 if let Some(rename) = self.rename.as_ref() {
316 serde_attrs.push(format!("rename = \"{rename}\""));
317 }
318 if let Some(casing) = &self.field_casing.as_maybe_str() {
319 serde_attrs.push(format!("rename_all = \"{casing}\""));
320 }
321 serde_attrs
322 }
323}
324
325impl Parse for VariantAttrs {
326 fn parse(input: ParseStream) -> Result<Self> {
327 let content;
328 parenthesized!(content in input);
329
330 let parse_value = || -> Result<String> {
331 content.parse::<Token![=]>()?;
332 Ok(content
333 .parse::<LitStr>()?
334 .to_token_stream()
335 .to_string()
336 .trim_matches('"')
337 .to_owned())
338 };
339
340 let mut result = Self::default();
341 loop {
342 let key: Ident = content.call(IdentExt::parse_any)?;
343 match key.to_string().as_ref() {
344 "rename" => result.rename = Some(parse_value()?),
345 "rename_all" => {
346 result.field_casing = Casing::try_from(parse_value()?.as_ref())
347 .map_err(|err| Error::new(content.span(), err))?
348 }
349 other => {
350 return Err(Error::new(
351 content.span(),
352 format!("Unexpected variant attribute: {other}"),
353 ))
354 }
355 }
356
357 if content.is_empty() {
358 break;
359 }
360
361 content.parse::<Token![,]>()?;
362 }
363
364 Ok(result)
365 }
366}
367
368fn is_path_to_primitive(ty: &syn::Type) -> bool {
369 matches!(
370 ty,
371 syn::Type::Path(TypePath { path, qself })
372 if qself.is_none()
373 && path
374 .get_ident()
375 .map(ToString::to_string)
376 .map(|ident| ident == "String" || Primitive::from_str(&ident).is_ok())
377 .unwrap_or(false)
378 )
379}