1use darling::{ast::Data, FromDeriveInput, FromField};
7use proc_macro::TokenStream;
8use proc_macro2::TokenStream as TokenStream2;
9use quote::{format_ident, quote};
10use syn::{parse_macro_input, DeriveInput, Ident, Type};
11
12#[derive(Debug, FromField)]
14#[darling(attributes(payrix))]
15struct PayrixField {
16 ident: Option<Ident>,
17 ty: Type,
18
19 #[darling(default)]
22 readonly: bool,
23
24 #[darling(default)]
27 create_only: bool,
28
29 #[darling(default)]
32 mutable: bool,
33
34 #[darling(default)]
37 create_required: bool,
38
39 #[darling(default)]
43 create_type: Option<String>,
44}
45
46#[derive(Debug, FromDeriveInput)]
48#[darling(attributes(payrix), supports(struct_named))]
49struct PayrixEntityArgs {
50 ident: Ident,
51 data: Data<(), PayrixField>,
52
53 #[darling(default)]
55 create: Option<Ident>,
56
57 #[darling(default)]
59 update: Option<Ident>,
60}
61
62fn get_serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
64 for attr in attrs {
65 if attr.path().is_ident("serde") {
66 if let Ok(nested) = attr.parse_args_with(
67 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
68 ) {
69 for meta in nested {
70 if let syn::Meta::NameValue(nv) = meta {
71 if nv.path.is_ident("rename") {
72 if let syn::Expr::Lit(syn::ExprLit {
73 lit: syn::Lit::Str(s),
74 ..
75 }) = nv.value
76 {
77 return Some(s.value());
78 }
79 }
80 }
81 }
82 }
83 }
84 }
85 None
86}
87
88fn is_option_type(ty: &Type) -> bool {
90 if let Type::Path(type_path) = ty {
91 if let Some(segment) = type_path.path.segments.last() {
92 return segment.ident == "Option";
93 }
94 }
95 false
96}
97
98fn is_vec_type(ty: &Type) -> bool {
100 if let Type::Path(type_path) = ty {
101 if let Some(segment) = type_path.path.segments.last() {
102 return segment.ident == "Vec";
103 }
104 }
105 false
106}
107
108fn is_bool_type(ty: &Type) -> bool {
110 if let Type::Path(type_path) = ty {
111 if let Some(segment) = type_path.path.segments.last() {
112 return segment.ident == "bool";
113 }
114 }
115 false
116}
117
118fn wrap_in_option(ty: &Type) -> TokenStream2 {
123 if is_option_type(ty) {
124 quote! { #ty }
125 } else if is_bool_type(ty) {
126 quote! { Option<bool> }
127 } else {
128 quote! { Option<#ty> }
129 }
130}
131
132struct RequestField {
134 name: Ident,
135 ty: Type,
136 rename: Option<String>,
137 required: bool,
139 override_type: Option<String>,
141}
142
143fn generate_request_type(
145 type_name: &Ident,
146 fields: &[RequestField],
147 is_create: bool,
148 source_name: &Ident,
149) -> TokenStream2 {
150 let field_defs: Vec<TokenStream2> = fields
151 .iter()
152 .map(|field| {
153 let name = &field.name;
154 let rename_attr = field.rename.as_ref().map(|r| {
155 quote! { #[serde(rename = #r)] }
156 });
157 let field_doc = format!("See [`{}`] for field documentation.", source_name);
158
159 let (field_ty, skip_attr) = if field.required {
161 if let Some(ref override_type) = field.override_type {
163 let ty: Type = syn::parse_str(override_type)
164 .expect("Invalid create_type value");
165 (quote! { #ty }, quote! {})
166 } else {
167 let ty = &field.ty;
169 if is_option_type(ty) {
170 if let Type::Path(type_path) = ty {
172 if let Some(segment) = type_path.path.segments.last() {
173 if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
174 if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
175 return quote! {
176 #[doc = #field_doc]
177 #rename_attr
178 pub #name: #inner
179 };
180 }
181 }
182 }
183 }
184 }
185 (quote! { #ty }, quote! {})
186 }
187 } else {
188 if let Some(ref override_type) = field.override_type {
190 let ty: Type = syn::parse_str(override_type)
191 .expect("Invalid create_type value");
192 (quote! { Option<#ty> }, quote! { #[serde(skip_serializing_if = "Option::is_none")] })
193 } else {
194 let wrapped_ty = wrap_in_option(&field.ty);
195 (wrapped_ty, quote! { #[serde(skip_serializing_if = "Option::is_none")] })
196 }
197 };
198
199 quote! {
200 #[doc = #field_doc]
201 #rename_attr
202 #skip_attr
203 pub #name: #field_ty
204 }
205 })
206 .collect();
207
208 let type_doc = if is_create {
209 format!("Request body for creating a new [`{}`].", source_name)
210 } else {
211 format!("Request body for updating an existing [`{}`].", source_name)
212 };
213
214 let has_required = fields.iter().any(|f| f.required);
216 let derives = if has_required {
217 quote! { #[derive(Debug, Clone, serde::Serialize)] }
218 } else {
219 quote! { #[derive(Debug, Clone, Default, serde::Serialize)] }
220 };
221
222 quote! {
223 #[doc = #type_doc]
224 #derives
225 #[serde(rename_all = "camelCase")]
226 pub struct #type_name {
227 #(#field_defs),*
228 }
229 }
230}
231
232#[proc_macro_derive(PayrixEntity, attributes(payrix))]
285pub fn derive_payrix_entity(input: TokenStream) -> TokenStream {
286 let input = parse_macro_input!(input as DeriveInput);
287
288 let original_fields: Vec<_> = if let syn::Data::Struct(data) = &input.data {
290 if let syn::Fields::Named(fields) = &data.fields {
291 fields.named.iter().collect()
292 } else {
293 Vec::new()
294 }
295 } else {
296 Vec::new()
297 };
298
299 let args = match PayrixEntityArgs::from_derive_input(&input) {
300 Ok(args) => args,
301 Err(e) => return TokenStream::from(e.write_errors()),
302 };
303
304 let struct_name = &args.ident;
305
306 let create_name = args
308 .create
309 .unwrap_or_else(|| format_ident!("Create{}", struct_name));
310 let update_name = args
311 .update
312 .unwrap_or_else(|| format_ident!("Update{}", struct_name));
313
314 let mut create_fields: Vec<RequestField> = Vec::new();
316 let mut update_fields: Vec<RequestField> = Vec::new();
317
318 let fields = match args.data {
319 Data::Struct(ref fields) => fields,
320 _ => panic!("PayrixEntity only supports structs"),
321 };
322
323 for (idx, field) in fields.iter().enumerate() {
324 let field_name = match &field.ident {
325 Some(name) => name.clone(),
326 None => continue,
327 };
328
329 if is_vec_type(&field.ty) {
331 continue;
332 }
333
334 let serde_rename = original_fields
336 .get(idx)
337 .and_then(|f| get_serde_rename(&f.attrs));
338
339 if field.readonly {
341 continue;
343 } else if field.create_only {
344 create_fields.push(RequestField {
346 name: field_name,
347 ty: field.ty.clone(),
348 rename: serde_rename,
349 required: field.create_required,
350 override_type: field.create_type.clone(),
351 });
352 } else if field.mutable {
353 create_fields.push(RequestField {
355 name: field_name.clone(),
356 ty: field.ty.clone(),
357 rename: serde_rename.clone(),
358 required: field.create_required,
359 override_type: field.create_type.clone(),
360 });
361 update_fields.push(RequestField {
363 name: field_name,
364 ty: field.ty.clone(),
365 rename: serde_rename,
366 required: false,
367 override_type: None,
368 });
369 }
370 }
372
373 let create_type = generate_request_type(&create_name, &create_fields, true, struct_name);
375 let update_type = generate_request_type(&update_name, &update_fields, false, struct_name);
376
377 let expanded = quote! {
378 #create_type
379
380 #update_type
381 };
382
383 TokenStream::from(expanded)
384}