1#![warn(missing_docs, non_ascii_idents, trivial_numeric_casts,
4 noop_method_call, single_use_lifetimes, trivial_casts,
5 unused_lifetimes, nonstandard_style, variant_size_differences)]
6#![deny(keyword_idents)]
7#![warn(clippy::missing_docs_in_private_items)]
8#![allow(clippy::needless_return, clippy::while_let_on_iterator)]
9
10
11use convert_case::Casing;
12use proc_macro::{self, TokenStream};
13use quote::{quote, quote_spanned, ToTokens};
14use syn::spanned::Spanned;
15use syn::{parse_macro_input, DeriveInput, Token, Ident, LitBool};
16
17#[proc_macro_derive(MetadataKind)]
19pub fn derive_metadata_kind(input: TokenStream) -> TokenStream {
20 let DeriveInput {ident, ..} = parse_macro_input!(input);
21
22 let output = quote! {
23 impl struct_metadata::MetadataKind for #ident {}
24 };
25
26 output.into()
27}
28
29#[proc_macro_derive(Described, attributes(metadata, metadata_type, metadata_sequence, serde))]
31pub fn derive(input: TokenStream) -> TokenStream {
32 let DeriveInput {ident, attrs, data, ..} = parse_macro_input!(input);
33
34 let metadata_type = parse_metadata_type(&attrs);
35 let serde_attrs = _parse_serde_attrs(&attrs);
36
37 let outer_name = match serde_attrs.rename {
39 Some(new_name) => quote!(#new_name),
40 None => quote_spanned!(ident.span() => stringify!(#ident)),
41 };
42
43 match data {
44 syn::Data::Struct(data) => {
45
46 let kind = match data.fields {
47 syn::Fields::Named(fields) => {
48 let mut children = vec![];
49 let mut flattened_children = vec![];
50 let mut flattened_metadata = vec![];
51
52 for field in &fields.named {
53 let SerdeFieldAttrs {rename, flatten, mut has_default, mut aliases } = _parse_serde_field_attrs(&field.attrs);
54 has_default |= serde_attrs.has_default;
55 let name = field.ident.clone().unwrap();
56 let ty = &field.ty;
57 let ty = quote_spanned!(ty.span() => <#ty as struct_metadata::Described::<#metadata_type>>::metadata());
58 let docs = parse_doc_comment(&field.attrs);
59 let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &field.attrs);
60
61 let name = if let Some(rename) = rename {
62 aliases.insert(0, rename.clone());
63 quote!(#rename)
64 } else if let Some(case) = serde_attrs.rename_all {
65 let new_name = name.to_string().to_case(case);
66 aliases.insert(0, new_name.clone());
67 quote!(#new_name)
68 } else {
69 aliases.insert(0, name.to_string());
70 quote!(stringify!(#name))
71 };
72
73 if flatten {
74 flattened_children.push(ty);
75 flattened_metadata.push(metadata);
76 } else {
77 children.push(quote!{struct_metadata::Entry::<#metadata_type> {
78 label: #name,
79 docs: #docs,
80 metadata: #metadata,
81 type_info: #ty,
82 has_default: #has_default,
83 aliases: &[#(#aliases),*]
84 }});
85 }
86 }
87
88 if flattened_children.is_empty() {
89 quote!(struct_metadata::Kind::Struct::<#metadata_type> {
90 name: #outer_name,
91 children: vec![#(#children),*]
92 })
93 } else {
94 quote!(struct_metadata::Kind::<#metadata_type>::new_struct(#outer_name, vec![#(#children),*], &mut [#(#flattened_children),*], &mut [#(#flattened_metadata),*]))
95 }
96 },
97 syn::Fields::Unnamed(fields) => {
98 if fields.unnamed.is_empty() {
99 quote!(struct_metadata::Kind::<#metadata_type>::Struct { name: #outer_name, children: vec![] })
100 } else if fields.unnamed.len() == 1 {
101 let ty = &fields.unnamed[0].ty;
102 let ty = quote_spanned!(ty.span() => <#ty as struct_metadata::Described::<#metadata_type>>::metadata());
103 quote!(struct_metadata::Kind::<#metadata_type>::Aliased { name: #outer_name, kind: Box::new(#ty)})
104 } else {
105 panic!("tuple struct not supported")
106 }
107 },
108 syn::Fields::Unit => {
109 quote!(struct_metadata::Kind::<#metadata_type>::Struct { name: #outer_name, children: vec![] })
110 },
111 };
112
113 let docs = parse_doc_comment(&attrs);
114 let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &attrs);
115 let output = quote! {
116 impl struct_metadata::Described::<#metadata_type> for #ident {
117 #[allow(clippy::needless_update)]
118 fn metadata() -> struct_metadata::Descriptor::<#metadata_type> {
119 let mut data = struct_metadata::Descriptor::<#metadata_type> {
120 docs: #docs,
121 kind: #kind,
122 metadata: #metadata,
123 };
124 data.propagate(None);
125 data
126 }
127 }
128 };
129
130 output.into()
131 }
132
133 syn::Data::Enum(data) => {
134
135 let mut all_variants = vec![];
136
137 for variant in data.variants {
138
139 if !variant.fields.is_empty() {
140 return syn::Error::new(variant.fields.span(), "Only enums without field values are supported.").into_compile_error().into()
141 }
142
143 let name = variant.ident.clone();
144 let docs = parse_doc_comment(&variant.attrs);
145 let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &variant.attrs);
146 let SerdeFieldAttrs {rename, flatten: _, has_default: _, mut aliases } = _parse_serde_field_attrs(&variant.attrs);
147
148 let name = if let Some(name) = rename {
149 aliases.insert(0, name.clone());
150 quote!(#name)
151 } else if let Some(case) = serde_attrs.rename_all {
152 let new_name = name.to_string().to_case(case);
153 aliases.insert(0, new_name.clone());
154 quote!(#new_name)
155 } else {
156 aliases.insert(0, name.to_string());
157 quote_spanned!(variant.span() => stringify!(#name))
158 };
159
160 all_variants.push(quote!{struct_metadata::Variant::<#metadata_type> {
161 label: #name,
162 docs: #docs,
163 metadata: #metadata,
164 aliases: &[#(#aliases),*]
165 }});
166 }
167
168 let docs = parse_doc_comment(&attrs);
169 let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &attrs);
170 let output = quote! {
171 impl struct_metadata::Described::<#metadata_type> for #ident {
172 fn metadata() -> struct_metadata::Descriptor::<#metadata_type> {
173 struct_metadata::Descriptor::<#metadata_type> {
174 docs: #docs,
175 kind: struct_metadata::Kind::<#metadata_type>::Enum {
176 name: #outer_name,
177 variants: vec![#(#all_variants),*]
178 },
179 metadata: #metadata,
180 }
181 }
182 }
183 };
184
185 output.into()
186 }
187
188 _ => {
189 panic!("derive is not supported for this type")
190 }
191 }
192}
193
194#[proc_macro_derive(DescribedEnumString, attributes(metadata, metadata_type, metadata_sequence, serde, strum))]
197pub fn derive_enum_string(input: TokenStream) -> TokenStream {
198 let DeriveInput {ident, attrs, data, ..} = parse_macro_input!(input);
199
200 let metadata_type = parse_metadata_type(&attrs);
201 let strum_attr = _parse_strum_attrs(&attrs);
202
203 match data {
204 syn::Data::Enum(data) => {
205
206 let mut all_variants = vec![];
207
208 for variant in data.variants {
209
210 if !variant.fields.is_empty() {
211 return syn::Error::new(variant.fields.span(), "Only enums without field values are supported.").into_compile_error().into()
212 }
213
214 let name = variant.ident.clone();
215 let docs = parse_doc_comment(&variant.attrs);
216 let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &variant.attrs);
217
218 let name = match strum_attr.serialize_all {
220 Some(case) => {
221 let new_name = name.to_string().to_case(case);
222 quote!(#new_name)
223 },
224 None => {
225 quote!(#name)
226 },
227 };
228
229 all_variants.push(quote!{struct_metadata::Variant::<#metadata_type> {
230 label: #name,
231 docs: #docs,
232 metadata: #metadata,
233 aliases: &[#name]
234 }});
235 }
236
237 let docs = parse_doc_comment(&attrs);
238 let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &attrs);
239 let output = quote! {
240 impl struct_metadata::Described::<#metadata_type> for #ident {
241 fn metadata() -> struct_metadata::Descriptor::<#metadata_type> {
242 struct_metadata::Descriptor::<#metadata_type> {
243 docs: #docs,
244 kind: struct_metadata::Kind::<#metadata_type>::Enum {
245 name: stringify!(#ident),
246 variants: vec![#(#all_variants),*]
247 },
248 metadata: #metadata,
249 }
250 }
251 }
252 };
253
254 output.into()
255 }
256
257 _ => {
258 panic!("DescribedEnumString only applies to enum types")
259 }
260 }
261}
262
263
264fn parse_doc_comment(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
267 let mut lines = vec![];
268 for attr in attrs {
269 if let syn::AttrStyle::Inner(_) = attr.style {
270 continue
271 }
272
273 if let syn::Meta::NameValue(pair) = &attr.meta {
274 if !pair.path.is_ident("doc") { continue }
275 if let Some(doc) = pair.value.span().source_text() {
276 let doc = doc.strip_prefix("///").unwrap_or(&doc);
277 lines.push(doc.trim().to_string());
278 }
279 }
280 }
281
282 if lines.is_empty() {
283 quote! { None }
284 } else {
285 quote!{ Some(vec![
286 #( #lines, )*
287 ])}
288 }
289}
290
291enum MetadataKind {
293 Type(proc_macro2::TokenStream, bool),
295 Sequence(proc_macro2::TokenStream),
297}
298
299impl ToTokens for MetadataKind {
300 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
301 match self {
302 MetadataKind::Type(has, _) => tokens.extend(has.clone()),
303 MetadataKind::Sequence(has) => tokens.extend(has.clone()),
304 }
305 }
306}
307
308fn parse_metadata_type(attrs: &[syn::Attribute]) -> MetadataKind {
310 let metadata_type = _parse_metadata_type(attrs);
311 let metadata_sequence = _parse_metadata_sequence(attrs);
312 match metadata_type {
313 Some((tokens, defaults)) => match metadata_sequence {
314 Some(_) => panic!("Only one of metadata_type and metadata_sequence may be set."),
315 None => MetadataKind::Type(tokens, defaults),
316 },
317 None => match metadata_sequence {
318 Some(tokens) => MetadataKind::Sequence(tokens),
319 None => MetadataKind::Sequence(quote!(std::collections::HashMap<&'static str, &'static str>)),
320 },
321 }
322}
323
324fn _parse_metadata_sequence(attrs: &[syn::Attribute]) -> Option<proc_macro2::TokenStream> {
328 for attr in attrs {
329 if let syn::Meta::List(meta) = &attr.meta {
330 if meta.path.is_ident("metadata_sequence") {
331 let MetadataType(name, _) = syn::parse2(meta.tokens.clone()).expect("Invalid metadata_sequence");
332 return Some(quote!{ #name })
333 }
334 }
335 }
336 None
337}
338
339fn _parse_metadata_type(attrs: &[syn::Attribute]) -> Option<(proc_macro2::TokenStream, bool)> {
342 for attr in attrs {
343 if let syn::Meta::List(meta) = &attr.meta {
344 if meta.path.is_ident("metadata_type") {
345 let MetadataType(name, defaults) = syn::parse2(meta.tokens.clone()).expect("Invalid metadata_type");
346 return Some((quote!{ #name }, defaults))
347 }
348 }
349 }
350 None
351}
352
353fn parse_metadata_params(metatype: &MetadataKind, attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
355 match metatype {
356 MetadataKind::Sequence(_) => {
357 for attr in attrs {
358 if let syn::Meta::List(meta) = &attr.meta {
359 if meta.path.is_ident("metadata") {
360 let MetadataParams (names, values) = syn::parse2(meta.tokens.clone())
361 .unwrap_or_else(|_| panic!("Invalid metadata attribute: {}", meta.tokens));
362 return quote!{ [
363 #((stringify!(#names), stringify!(#values).into())),*
364 ].into_iter().collect() }
365 }
366 }
367 }
368 quote!{ Default::default() }
369 },
370 MetadataKind::Type(type_name, defaults) => {
371 let defaults = if *defaults {
372 quote!(..Default::default())
373 } else {
374 quote!()
375 };
376
377 for attr in attrs {
378 if let syn::Meta::List(meta) = &attr.meta {
379 if meta.path.is_ident("metadata") {
380 let MetadataParams (names, values) = syn::parse2(meta.tokens.clone())
381 .unwrap_or_else(|_| panic!("Invalid metadata attribute: {}", meta.tokens));
382 return quote!{
383 #type_name {
384 #(#names: #values.into(),)*
385 #defaults
386 }
387 }
388 }
389 }
390 }
391 quote!{ Default::default() }
392 }
393 }
394}
395
396struct MetadataType(syn::Type, bool);
398impl syn::parse::Parse for MetadataType {
399 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
400 let key = input.parse()?;
403
404 if input.is_empty() {
405 return Ok(MetadataType(key, true));
406 }
407
408 let defaults;
409
410 input.parse::<Token![,]>()?;
411
412 let param: Ident = input.parse()?;
413 if input.peek(Token![:]) {
414 input.parse::<Token![:]>()?;
415 } else {
416 input.parse::<Token![=]>()?;
417 }
418 let value: LitBool = input.parse()?;
419
420 if param == "defaults" {
421 defaults = value.value;
422 } else {
423 panic!("Unknown type parameter: {param}")
424 }
425
426 Ok(MetadataType(key, defaults))
427 }
428}
429
430struct MetadataParams(Vec<syn::Ident>, Vec<syn::Expr>);
432impl syn::parse::Parse for MetadataParams {
433 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
434 let mut names = vec![];
438 let mut values = vec![];
439
440 loop {
441 if input.is_empty() {
442 break
443 }
444
445 let key = input.parse()?;
446 if input.peek(Token![:]) {
447 input.parse::<Token![:]>()?;
448 } else {
449 input.parse::<Token![=]>()?;
450 }
451 let value = input.parse()?;
452 names.push(key);
453 values.push(value);
454
455 if input.is_empty() {
456 break
457 }
458 input.parse::<Token![,]>()?;
459 }
460
461 Ok(MetadataParams(names, values))
462 }
463}
464
465fn _parse_serde_field_attrs(attrs: &[syn::Attribute]) -> SerdeFieldAttrs {
467 for attr in attrs {
468 if let syn::Meta::List(meta) = &attr.meta {
469 if meta.path.is_ident("serde") {
470 return syn::parse2(meta.tokens.clone()).expect("Invalid serde");
471 }
472 }
473 }
474 Default::default()
475}
476
477#[derive(Default)]
479struct SerdeFieldAttrs {
480 rename: Option<String>,
482 flatten: bool,
485 has_default: bool,
487 aliases: Vec<String>
489}
490
491impl syn::parse::Parse for SerdeFieldAttrs {
492 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
493 let mut out = SerdeFieldAttrs::default();
497 loop {
501 let key: syn::Ident = input.parse()?;
502 if input.peek(Token![=]) {
503 input.parse::<Token![=]>()?;
504
505 let value: syn::LitStr = input.parse()?;
506
507 if key == "rename" {
508 out.rename = Some(value.value());
509 }
510
511 if key == "alias" {
512 out.aliases.push(value.value());
513 }
514 }
515
516 if key == "default" {
517 out.has_default = true;
518 }
519
520 if key == "flatten" {
521 out.flatten = true;
522 }
523
524 if input.is_empty() {
525 break
526 }
527 input.parse::<Token![,]>()?;
528 }
529
530Ok(out)
532 }
533}
534
535
536fn _parse_serde_attrs(attrs: &[syn::Attribute]) -> SerdeAttrs {
538 for attr in attrs {
539 if let syn::Meta::List(meta) = &attr.meta {
540 if meta.path.is_ident("serde") {
541 return syn::parse2(meta.tokens.clone()).expect("Invalid serde");
542 }
543 }
544 }
545 Default::default()
546}
547
548#[derive(Default)]
550struct SerdeAttrs {
551 rename: Option<String>,
553 rename_all: Option<convert_case::Case>,
555 has_default: bool,
557}
558
559impl syn::parse::Parse for SerdeAttrs {
560 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
561 let mut out = SerdeAttrs::default();
565 loop {
569 let key: syn::Ident = input.parse()?;
570 if input.peek(Token![=]) {
571 input.parse::<Token![=]>()?;
572
573 let value: syn::LitStr = input.parse()?;
574
575 if key == "rename" {
576 out.rename = Some(value.value());
577 }
578
579 if key == "rename_all" {
580 out.rename_all = Some(fetch_case(&value)?);
581 }
582 }
583
584 if key == "default" {
585 out.has_default = true;
586 }
587
588 if input.is_empty() {
589 break
590 }
591 input.parse::<Token![,]>()?;
592 }
593
594 Ok(out)
595 }
596}
597
598
599fn _parse_strum_attrs(attrs: &[syn::Attribute]) -> StrumAttrs {
601 for attr in attrs {
602 if let syn::Meta::List(meta) = &attr.meta {
603 if meta.path.is_ident("strum") {
604 return syn::parse2(meta.tokens.clone()).expect("Invalid strum");
605 }
606 }
607 }
608 Default::default()
609}
610
611#[derive(Default)]
613struct StrumAttrs {
614 serialize_all: Option<convert_case::Case>,
616}
617
618impl syn::parse::Parse for StrumAttrs {
619 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
620 let mut out = StrumAttrs::default();
624 loop {
628 let key: syn::Ident = input.parse()?;
629 if input.peek(Token![=]) {
630 input.parse::<Token![=]>()?;
631
632 let value: syn::LitStr = input.parse()?;
633
634 if key == "serialize_all" {
635 out.serialize_all = Some(fetch_case(&value)?);
636 }
637 }
638
639 if input.is_empty() {
640 break
641 }
642 input.parse::<Token![,]>()?;
643 }
644
645 Ok(out)
646 }
647}
648
649
650fn fetch_case(name: &syn::LitStr) -> syn::Result<convert_case::Case> {
652 Ok(match name.value().to_lowercase().as_str() {
653 "lowercase" | "lower" => convert_case::Case::Lower,
654 "uppercase" | "upper" => convert_case::Case::Upper,
655 "pascalcase" | "pascal" | "uppercamel" => convert_case::Case::Pascal,
656 "camelcase" | "camel" => convert_case::Case::Camel,
657 "snake_case" => convert_case::Case::Snake,
658 "upper_snake_case" | "screaming_snake_case" => convert_case::Case::UpperSnake,
659 "kebab_case" => convert_case::Case::Kebab,
660 "upper_kebab_case" | "screaming_kebab_case" => convert_case::Case::UpperKebab,
661 _ => return Err(syn::Error::new(name.span(), format!("Unsupported case string: {}", name.value())))
662 })
663}