actix_prost_macros/
lib.rs

1use enums::process_enum;
2use field::process_field;
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{Attribute, AttributeArgs, Item};
6
7mod enums;
8mod field;
9mod prost;
10
11fn find_rename_all(attrs: &[syn::NestedMeta]) -> Option<String> {
12    for attr in attrs {
13        match attr {
14            syn::NestedMeta::Meta(syn::Meta::NameValue(meta))
15                if meta.path == syn::parse_quote!(rename_all) =>
16            {
17                if let syn::Lit::Str(s) = &meta.lit {
18                    return Some(s.value());
19                }
20            }
21            _ => {}
22        }
23    }
24    None
25}
26
27/// Checks if any other `actix_prost_macros::serde` attribute exists
28/// for the item defined **after** the current attribute.
29fn has_other_actix_prost_serde_attributes(item_attributes: &[Attribute]) -> bool {
30    item_attributes
31        .iter()
32        .any(|attribute| attribute.path == syn::parse_quote!(actix_prost_macros::serde))
33}
34
35#[proc_macro_attribute]
36pub fn serde(attrs: TokenStream, item: TokenStream) -> TokenStream {
37    let mut item = syn::parse_macro_input!(item as Item);
38
39    let attrs = syn::parse_macro_input!(attrs as AttributeArgs);
40    let maybe_rename = match find_rename_all(&attrs) {
41        // 'none' option makes it possible to use rust default case
42        // by default (which is snake_case for structs and PascalCase for enums),
43        // and overwrite that value for some of the messages. For us, it is a way
44        // to make most of the messages using snake_case, while small part of them
45        // using camelCase.
46        Some(rename) if rename.to_lowercase() == "none" => None,
47        Some(rename) => Some(rename),
48        None => Some("camelCase".to_owned()),
49    };
50
51    // Checking that no other `actix_prost_macros::serde` attributes exist for an item
52    // allows us to override 'rename_all' attribute for the type.
53    // That allows to specify a default serialization case convention and override it
54    // for some of the types only (e.g., for legacy reasons).
55    match &mut item {
56        syn::Item::Enum(item) if !has_other_actix_prost_serde_attributes(&item.attrs) => {
57            let mut need_serde_as = false;
58            for variant in item.variants.iter_mut() {
59                for field in variant.fields.iter_mut() {
60                    let (attr, need) = process_field(field);
61                    need_serde_as = need || need_serde_as;
62                    if let Some(attr) = attr {
63                        field.attrs.push(attr);
64                    }
65                }
66            }
67            if need_serde_as {
68                item.attrs.push(syn::parse_quote!(#[serde_with::serde_as]));
69            }
70            item.attrs
71                .push(syn::parse_quote!(#[derive(serde::Serialize, serde::Deserialize)]));
72            process_enum(item, maybe_rename.clone());
73        }
74        syn::Item::Struct(item) if !has_other_actix_prost_serde_attributes(&item.attrs) => {
75            let mut need_serde_as = false;
76            for field in item.fields.iter_mut() {
77                let (attr, need) = process_field(field);
78                need_serde_as = need || need_serde_as;
79                if let Some(attr) = attr {
80                    field.attrs.push(attr);
81                }
82            }
83            if need_serde_as {
84                item.attrs.push(syn::parse_quote!(#[serde_with::serde_as]));
85            }
86            item.attrs
87                .push(syn::parse_quote!(#[derive(serde::Serialize, serde::Deserialize)]));
88            if let Some(rename) = maybe_rename {
89                item.attrs
90                    .push(syn::parse_quote!(#[serde(rename_all = #rename)]));
91            }
92        }
93        _ => {}
94    }
95    quote!(#item).into()
96}