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 data: darling::ast::Data<VariantContainer, FieldContainer>,
46 #[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}