google_cloud_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use darling::{FromDeriveInput, FromField, FromMeta, FromVariant};
6use quote::quote;
7use syn::parse_macro_input;
8
9mod casing;
10
11use crate::casing::{transform_field_casing, transform_variant_casing};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, FromMeta)]
14pub(crate) enum RenameAll {
15    #[darling(rename = "lowercase")]
16    LowerCase,
17    #[darling(rename = "UPPERCASE")]
18    UpperCase,
19    #[darling(rename = "PascalCase")]
20    PascalCase,
21    #[darling(rename = "camelCase")]
22    CamelCase,
23    #[darling(rename = "snake_case")]
24    SnakeCase,
25    #[darling(rename = "SCREAMING_SNAKE_CASE")]
26    ScreamingSnakeCase,
27    #[darling(rename = "kebab-case")]
28    KebabCase,
29    #[darling(rename = "SCREAMING-KEBAB-CASE")]
30    ScreamingKebabCase,
31}
32
33impl Default for RenameAll {
34    fn default() -> RenameAll {
35        RenameAll::CamelCase
36    }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, FromDeriveInput)]
40#[darling(attributes(datastore), supports(struct_named, enum_unit))]
41struct Container {
42    pub ident: syn::Ident,
43    // pub vis: syn::Visibility,
44    // pub generics: syn::Generics,
45    pub data: darling::ast::Data<VariantContainer, FieldContainer>,
46    // pub attrs: Vec<syn::Attribute>,
47    #[darling(default)]
48    pub rename_all: RenameAll,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, FromVariant)]
52#[darling(attributes(datastore))]
53struct VariantContainer {
54    pub ident: syn::Ident,
55    #[darling(default)]
56    pub rename: Option<String>,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, FromField)]
60#[darling(attributes(datastore))]
61struct FieldContainer {
62    pub ident: Option<syn::Ident>,
63    #[darling(default)]
64    pub rename: Option<String>,
65}
66
67fn derive_into_value_struct(
68    ident: syn::Ident,
69    fields: Vec<FieldContainer>,
70    rename_all: RenameAll,
71) -> TokenStream {
72    let idents: Vec<syn::Ident> = fields
73        .iter()
74        .map(|field| field.ident.clone().unwrap())
75        .collect();
76    let names: Vec<syn::LitStr> = fields
77        .into_iter()
78        .map(|field| {
79            let renamed = field.rename;
80            let field = field.ident.unwrap();
81            let span = field.span();
82            let name = renamed.unwrap_or_else(|| transform_field_casing(field, rename_all));
83            syn::LitStr::new(name.as_str(), span)
84        })
85        .collect();
86
87    let capacity = names.len();
88
89    let tokens = quote! {
90        impl ::google_cloud::datastore::IntoValue for #ident {
91            fn into_value(self) -> ::google_cloud::datastore::Value {
92                let mut props = ::std::collections::HashMap::with_capacity(#capacity);
93                #(props.insert(::std::string::String::from(#names), self.#idents.into_value());)*
94                ::google_cloud::datastore::Value::EntityValue(props)
95            }
96        }
97    };
98
99    tokens.into()
100}
101
102fn derive_into_value_enum(
103    ident: syn::Ident,
104    variants: Vec<VariantContainer>,
105    rename_all: RenameAll,
106) -> TokenStream {
107    let idents: Vec<syn::Ident> = variants
108        .iter()
109        .map(|variant| variant.ident.clone())
110        .collect();
111    let names: Vec<syn::LitStr> = variants
112        .into_iter()
113        .map(|variant| {
114            let renamed = variant.rename;
115            let variant = variant.ident;
116            let span = variant.span();
117            let name = renamed.unwrap_or_else(|| transform_variant_casing(variant, rename_all));
118            syn::LitStr::new(name.as_str(), span)
119        })
120        .collect();
121
122    let tokens = quote! {
123        impl ::google_cloud::datastore::IntoValue for #ident {
124            fn into_value(self) -> ::google_cloud::datastore::Value {
125                match self {
126                    #(#ident::#idents => ::google_cloud::datastore::Value::StringValue(#names.to_string()),)*
127                }
128            }
129        }
130    };
131
132    tokens.into()
133}
134
135#[proc_macro_derive(IntoValue, attributes(datastore))]
136pub fn derive_into_value(input: TokenStream) -> TokenStream {
137    let input = parse_macro_input!(input as syn::DeriveInput);
138    let container = Container::from_derive_input(&input).unwrap();
139
140    let ident = container.ident;
141    let rename_all = container.rename_all;
142
143    match container.data {
144        darling::ast::Data::Enum(variants) => derive_into_value_enum(ident, variants, rename_all),
145        darling::ast::Data::Struct(darling::ast::Fields { fields, .. }) => {
146            derive_into_value_struct(ident, fields, rename_all)
147        }
148    }
149}
150
151fn derive_from_value_struct(
152    ident: syn::Ident,
153    fields: Vec<FieldContainer>,
154    rename_all: RenameAll,
155) -> TokenStream {
156    let idents: Vec<syn::Ident> = fields
157        .iter()
158        .map(|field| field.ident.clone().unwrap())
159        .collect();
160    let names: Vec<syn::LitStr> = fields
161        .into_iter()
162        .map(|field| {
163            let renamed = field.rename;
164            let field = field.ident.unwrap();
165            let span = field.span();
166            let name = renamed.unwrap_or_else(|| transform_field_casing(field, rename_all));
167            syn::LitStr::new(name.as_str(), span)
168        })
169        .collect();
170
171    let tokens = quote! {
172        impl ::google_cloud::datastore::FromValue for #ident {
173            fn from_value(value: ::google_cloud::datastore::Value) -> ::std::result::Result<#ident, ::google_cloud::error::ConvertError> {
174                let mut props = match value {
175                    ::google_cloud::datastore::Value::EntityValue(props) => props,
176                    _ => return ::std::result::Result::Err(
177                        ::google_cloud::error::ConvertError::UnexpectedPropertyType {
178                            expected: ::std::string::String::from("entity"),
179                            got: ::std::string::String::from(value.type_name()),
180                        }
181                    ),
182                };
183                let value = #ident {
184                    #(#idents: {
185                        let prop = props
186                            .remove(#names)
187                            .ok_or_else(|| {
188                                ::google_cloud::error::ConvertError::MissingProperty(::std::string::String::from(#names))
189                            })?;
190                        let value = ::google_cloud::datastore::FromValue::from_value(prop)?;
191                        value
192                    },)*
193                };
194                ::std::result::Result::Ok(value)
195            }
196        }
197    };
198
199    tokens.into()
200}
201
202fn derive_from_value_enum(
203    ident: syn::Ident,
204    variants: Vec<VariantContainer>,
205    rename_all: RenameAll,
206) -> TokenStream {
207    let idents: Vec<syn::Ident> = variants
208        .iter()
209        .map(|variant| variant.ident.clone())
210        .collect();
211    let names: Vec<syn::LitStr> = variants
212        .into_iter()
213        .map(|variant| {
214            let renamed = variant.rename;
215            let variant = variant.ident;
216            let span = variant.span();
217            let name = renamed.unwrap_or_else(|| transform_variant_casing(variant, rename_all));
218            syn::LitStr::new(name.as_str(), span)
219        })
220        .collect();
221
222    let tokens = quote! {
223        impl ::google_cloud::datastore::FromValue for #ident {
224            fn from_value(value: ::google_cloud::datastore::Value) -> ::std::result::Result<#ident, ::google_cloud::error::ConvertError> {
225                let value = match value {
226                    ::google_cloud::datastore::Value::StringValue(value) => value,
227                    _ => return ::std::result::Result::Err(
228                        ::google_cloud::error::ConvertError::UnexpectedPropertyType {
229                            expected: ::std::string::String::from("entity"),
230                            got: ::std::string::String::from(value.type_name()),
231                        }
232                    ),
233                };
234                match value.as_str() {
235                    #(#names => ::std::result::Result::Ok(#ident::#idents),)*
236                    _ => todo!("[datastore-derive] unknown enum variant encountered"),
237                }
238            }
239        }
240    };
241
242    tokens.into()
243}
244
245#[proc_macro_derive(FromValue, attributes(datastore))]
246pub fn derive_from_value(input: TokenStream) -> TokenStream {
247    let input = parse_macro_input!(input as syn::DeriveInput);
248    let container = Container::from_derive_input(&input).unwrap();
249
250    let ident = container.ident;
251    let rename_all = container.rename_all;
252
253    match container.data {
254        darling::ast::Data::Enum(variants) => derive_from_value_enum(ident, variants, rename_all),
255        darling::ast::Data::Struct(darling::ast::Fields { fields, .. }) => {
256            derive_from_value_struct(ident, fields, rename_all)
257        }
258    }
259}