1use proc_macro::TokenStream;
8use quote::quote;
9use syn::{parse_macro_input, DeriveInput};
10
11mod status_patterns;
13mod brazilian_patterns;
14mod validation_patterns;
15mod identifier_patterns;
16
17mod payment_patterns;
19mod wallet_patterns;
20mod repository_helpers;
21mod subscription_patterns;
22
23#[proc_macro_derive(DomainModel, attributes(domain, field))]
31pub fn derive_domain_model(input: TokenStream) -> TokenStream {
32 let input = parse_macro_input!(input as DeriveInput);
33 let struct_name = &input.ident;
34
35 eprintln!("[pleme-codegen] DomainModel pattern applied to {}", struct_name);
37
38 let expanded = quote! {
39 impl #struct_name {
40 pub fn cache_key(&self) -> String {
42 let product = std::env::var("PRODUCT").unwrap_or_else(|_| "default".to_string());
43 let key = format!("{}:{}:{}",
44 product,
45 stringify!(#struct_name).to_lowercase(),
46 uuid::Uuid::new_v4()
47 );
48
49 tracing::debug!(
51 entity = %stringify!(#struct_name),
52 product = %product,
53 cache_key = %key,
54 "Generated cache key for domain model"
55 );
56
57 key
58 }
59
60 pub const TABLE_NAME: &'static str = concat!(stringify!(#struct_name), "s");
62
63 pub fn create_audit_log(&self, action: &str, user_id: Option<uuid::Uuid>) -> serde_json::Value {
65 let audit_entry = serde_json::json!({
66 "entity_type": stringify!(#struct_name),
67 "action": action,
68 "user_id": user_id,
69 "timestamp": chrono::Utc::now().to_rfc3339(),
70 "product": std::env::var("PRODUCT").unwrap_or_else(|_| "default".to_string()),
71 "service": std::env::var("SERVICE_NAME").unwrap_or_else(|_| "unknown".to_string())
72 });
73
74 tracing::info!(
76 entity = %stringify!(#struct_name),
77 action = %action,
78 user_id = ?user_id,
79 "Domain model action recorded"
80 );
81
82 audit_entry
83 }
84
85 pub fn cache_key_with_ttl(&self, ttl_seconds: u64) -> (String, u64) {
87 (self.cache_key(), ttl_seconds)
88 }
89
90 pub fn track_repository_operation(&self, operation: &str, duration_ms: u64) {
92 tracing::info!(
93 entity = %stringify!(#struct_name),
94 operation = %operation,
95 duration_ms = %duration_ms,
96 "Repository operation completed"
97 );
98
99 }
101 }
102 };
103
104 TokenStream::from(expanded)
105}
106
107#[proc_macro_derive(GraphQLBridge, attributes(graphql))]
109pub fn derive_graphql_bridge(input: TokenStream) -> TokenStream {
110 let input = parse_macro_input!(input as DeriveInput);
111 let struct_name = &input.ident;
112
113 eprintln!("[pleme-codegen] GraphQLBridge pattern applied to {}", struct_name);
114
115 let expanded = quote! {
116 impl #struct_name {
117 pub fn to_graphql(&self) -> String {
119 let mut json_value: serde_json::Value = match serde_json::to_value(self) {
120 Ok(value) => value,
121 Err(e) => {
122 tracing::error!(
123 entity = %stringify!(#struct_name),
124 error = %e,
125 "Failed to serialize entity for GraphQL"
126 );
127 return "{}".to_string();
128 }
129 };
130
131 Self::convert_types_for_graphql(&mut json_value);
133
134 tracing::trace!(
136 entity = %stringify!(#struct_name),
137 "GraphQL conversion completed"
138 );
139
140 serde_json::to_string(&json_value)
141 .unwrap_or_else(|e| {
142 tracing::error!(
143 entity = %stringify!(#struct_name),
144 error = %e,
145 "Failed to serialize converted GraphQL value"
146 );
147 "{}".to_string()
148 })
149 }
150
151 fn convert_types_for_graphql(value: &mut serde_json::Value) {
153 match value {
154 serde_json::Value::Object(map) => {
155 for (key, v) in map.iter_mut() {
156 if key.contains("price") || key.contains("amount") || key.contains("total") || key.contains("tax") {
158 if let serde_json::Value::String(decimal_str) = v {
159 if let Ok(decimal_val) = decimal_str.parse::<f64>() {
160 *v = serde_json::Value::Number(
161 serde_json::Number::from_f64(decimal_val)
162 .unwrap_or(serde_json::Number::from(0))
163 );
164 }
165 }
166 }
167 Self::convert_types_for_graphql(v);
168 }
169 }
170 serde_json::Value::Array(arr) => {
171 for v in arr.iter_mut() {
172 Self::convert_types_for_graphql(v);
173 }
174 }
175 _ => {}
176 }
177 }
178
179 pub fn validate_for_graphql(&self) -> Result<(), String> {
181 tracing::debug!(
183 entity = %stringify!(#struct_name),
184 "GraphQL validation completed"
185 );
186 Ok(())
187 }
188
189 pub fn track_graphql_operation(&self, operation: &str, duration_ms: u64) {
191 tracing::info!(
192 entity = %stringify!(#struct_name),
193 operation = %operation,
194 duration_ms = %duration_ms,
195 "GraphQL operation completed"
196 );
197 }
198 }
199 };
200
201 TokenStream::from(expanded)
202}
203
204#[proc_macro_derive(BrazilianEntity, attributes(brazilian))]
206pub fn derive_brazilian_entity(input: TokenStream) -> TokenStream {
207 let input = parse_macro_input!(input as DeriveInput);
208 let struct_name = &input.ident;
209
210 eprintln!("[pleme-codegen] BrazilianEntity pattern applied to {}", struct_name);
211
212 let expanded = quote! {
213 impl #struct_name {
214 pub fn validate_cpf(cpf: &str) -> bool {
216 let digits: String = cpf.chars().filter(|c| c.is_ascii_digit()).collect();
217
218 if digits.len() != 11 {
220 tracing::debug!(cpf_length = %digits.len(), "CPF validation failed: invalid length");
221 return false;
222 }
223
224 if digits.chars().all(|c| c == digits.chars().next().unwrap()) {
226 tracing::debug!("CPF validation failed: all digits are the same");
227 return false;
228 }
229
230 let digits: Vec<u32> = digits.chars()
232 .map(|c| c.to_digit(10).unwrap_or(0))
233 .collect();
234
235 let sum1: u32 = (0..9).map(|i| digits[i] * (10 - i as u32)).sum();
237 let digit1 = match sum1 % 11 {
238 0 | 1 => 0,
239 n => 11 - n,
240 };
241
242 if digits[9] != digit1 {
243 tracing::debug!("CPF validation failed: first verification digit mismatch");
244 return false;
245 }
246
247 let sum2: u32 = (0..10).map(|i| digits[i] * (11 - i as u32)).sum();
249 let digit2 = match sum2 % 11 {
250 0 | 1 => 0,
251 n => 11 - n,
252 };
253
254 let is_valid = digits[10] == digit2;
255
256 tracing::debug!(
258 entity = %stringify!(#struct_name),
259 validation_result = %is_valid,
260 "CPF validation completed"
261 );
262
263 is_valid
264 }
265
266 pub fn format_cpf(cpf: &str) -> String {
268 let digits: String = cpf.chars().filter(|c| c.is_ascii_digit()).collect();
269 if digits.len() == 11 {
270 format!("{}.{}.{}-{}",
271 &digits[0..3], &digits[3..6],
272 &digits[6..9], &digits[9..11])
273 } else {
274 cpf.to_string()
275 }
276 }
277
278 pub fn validate_cep(cep: &str) -> bool {
280 let digits: String = cep.chars().filter(|c| c.is_ascii_digit()).collect();
281 let is_valid = digits.len() == 8 && !digits.chars().all(|c| c == '0');
282
283 tracing::debug!(
284 entity = %stringify!(#struct_name),
285 cep_length = %digits.len(),
286 validation_result = %is_valid,
287 "CEP validation completed"
288 );
289
290 is_valid
291 }
292
293 pub fn format_cep(cep: &str) -> String {
295 let digits: String = cep.chars().filter(|c| c.is_ascii_digit()).collect();
296 if digits.len() == 8 {
297 format!("{}-{}", &digits[0..5], &digits[5..8])
298 } else {
299 cep.to_string()
300 }
301 }
302
303 pub fn validate_cnpj(cnpj: &str) -> bool {
305 let digits: String = cnpj.chars().filter(|c| c.is_ascii_digit()).collect();
306
307 if digits.len() != 14 {
308 tracing::debug!(cnpj_length = %digits.len(), "CNPJ validation failed: invalid length");
309 return false;
310 }
311
312 if digits.chars().all(|c| c == digits.chars().next().unwrap()) {
314 tracing::debug!("CNPJ validation failed: all digits are the same");
315 return false;
316 }
317
318 let digits: Vec<u32> = digits.chars()
319 .map(|c| c.to_digit(10).unwrap_or(0))
320 .collect();
321
322 let weights1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
324 let sum1: u32 = (0..12).map(|i| digits[i] * weights1[i]).sum();
325 let digit1 = match sum1 % 11 {
326 0 | 1 => 0,
327 n => 11 - n,
328 };
329
330 if digits[12] != digit1 {
331 tracing::debug!("CNPJ validation failed: first verification digit mismatch");
332 return false;
333 }
334
335 let weights2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
337 let sum2: u32 = (0..13).map(|i| digits[i] * weights2[i]).sum();
338 let digit2 = match sum2 % 11 {
339 0 | 1 => 0,
340 n => 11 - n,
341 };
342
343 let is_valid = digits[13] == digit2;
344
345 tracing::debug!(
346 entity = %stringify!(#struct_name),
347 validation_result = %is_valid,
348 "CNPJ validation completed"
349 );
350
351 is_valid
352 }
353
354 pub fn format_cnpj(cnpj: &str) -> String {
356 let digits: String = cnpj.chars().filter(|c| c.is_ascii_digit()).collect();
357 if digits.len() == 14 {
358 format!("{}.{}.{}/{}-{}",
359 &digits[0..2], &digits[2..5], &digits[5..8],
360 &digits[8..12], &digits[12..14])
361 } else {
362 cnpj.to_string()
363 }
364 }
365
366 pub fn validate_brazilian_phone(phone: &str) -> bool {
368 let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect();
369 let is_valid = digits.len() == 10 || digits.len() == 11;
371
372 tracing::debug!(
373 entity = %stringify!(#struct_name),
374 phone_length = %digits.len(),
375 validation_result = %is_valid,
376 "Brazilian phone validation completed"
377 );
378
379 is_valid
380 }
381
382 pub fn format_brazilian_phone(phone: &str) -> String {
384 let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect();
385 match digits.len() {
386 10 => format!("({}) {}-{}", &digits[0..2], &digits[2..6], &digits[6..10]),
387 11 => format!("({}) {} {}-{}", &digits[0..2], &digits[2..3], &digits[3..7], &digits[7..11]),
388 _ => phone.to_string()
389 }
390 }
391
392 pub fn track_brazilian_validation(&self, validation_type: &str, success: bool) {
394 tracing::info!(
395 entity = %stringify!(#struct_name),
396 validation_type = %validation_type,
397 success = %success,
398 "Brazilian validation completed"
399 );
400 }
401 }
402 };
403
404 TokenStream::from(expanded)
405}
406
407#[proc_macro_derive(SmartRepository, attributes(repository))]
410pub fn derive_smart_repository(input: TokenStream) -> TokenStream {
411 let input = parse_macro_input!(input as DeriveInput);
412 let struct_name = &input.ident;
413
414 eprintln!("[pleme-codegen] SmartRepository pattern applied to {}", struct_name);
415
416 let expanded = quote! {
417 impl #struct_name {
418 pub async fn create_with_observability<T>(&self, entity: &T, user_id: Option<uuid::Uuid>)
420 -> Result<T, Box<dyn std::error::Error + Send + Sync>>
421 where
422 T: serde::Serialize + serde::de::DeserializeOwned + Clone,
423 {
424 let start = std::time::Instant::now();
425
426 tracing::info!(
427 repository = %stringify!(#struct_name),
428 operation = "CREATE_WITH_OBSERVABILITY",
429 user_id = ?user_id,
430 "Repository operation starting"
431 );
432
433 let result = Ok(entity.clone());
435
436 let duration = start.elapsed().as_millis() as u64;
438 tracing::info!(
439 repository = %stringify!(#struct_name),
440 operation = "CREATE",
441 duration_ms = %duration,
442 success = %result.is_ok(),
443 "Repository operation completed"
444 );
445
446 result
447 }
448
449 pub async fn find_with_smart_cache<T>(&self, id: &str) -> Result<Option<T>, Box<dyn std::error::Error + Send + Sync>>
451 where
452 T: serde::Serialize + serde::de::DeserializeOwned + Clone + Default,
453 {
454 let cache_key = format!("{}:{}", stringify!(#struct_name).to_lowercase(), id);
455
456 tracing::debug!(
457 repository = %stringify!(#struct_name),
458 cache_key = %cache_key,
459 "Smart cache lookup initiated"
460 );
461
462 let start = std::time::Instant::now();
463 let result = Ok(Some(T::default())); let duration = start.elapsed().as_millis() as u64;
465
466 tracing::info!(
467 repository = %stringify!(#struct_name),
468 operation = "FIND_WITH_CACHE",
469 duration_ms = %duration,
470 cache_miss = true,
471 success = %result.is_ok(),
472 "Repository operation completed"
473 );
474
475 result
476 }
477 }
478 };
479
480 TokenStream::from(expanded)
481}
482
483#[proc_macro_derive(SmartService, attributes(service))]
485pub fn derive_smart_service(input: TokenStream) -> TokenStream {
486 let input = parse_macro_input!(input as DeriveInput);
487 let struct_name = &input.ident;
488
489 eprintln!("[pleme-codegen] SmartService pattern applied to {}", struct_name);
490
491 let expanded = quote! {
492 impl #struct_name {
493 pub async fn execute_with_resilience<T>(&self, operation_name: &str, result: T) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
495 let start = std::time::Instant::now();
496
497 tracing::info!(
498 service = %stringify!(#struct_name),
499 operation = %operation_name,
500 "Service operation with resilience starting"
501 );
502
503 let duration = start.elapsed().as_millis() as u64;
504 tracing::info!(
505 service = %stringify!(#struct_name),
506 operation = %operation_name,
507 duration_ms = %duration,
508 "Service operation completed successfully"
509 );
510
511 Ok(result)
512 }
513
514 pub async fn health_check_comprehensive(&self) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
516 let health_data = serde_json::json!({
517 "service": stringify!(#struct_name),
518 "status": "healthy",
519 "timestamp": chrono::Utc::now().to_rfc3339(),
520 "checks": {
521 "database": {"status": "healthy"},
522 "cache": {"status": "healthy"}
523 }
524 });
525
526 tracing::debug!(
527 service = %stringify!(#struct_name),
528 health_status = "healthy",
529 "Health check completed"
530 );
531
532 Ok(health_data)
533 }
534 }
535 };
536
537 TokenStream::from(expanded)
538}
539
540#[proc_macro_derive(ArchitecturalMonitor, attributes(monitor))]
542pub fn derive_architectural_monitor(input: TokenStream) -> TokenStream {
543 let input = parse_macro_input!(input as DeriveInput);
544 let struct_name = &input.ident;
545
546 eprintln!("[pleme-codegen] ArchitecturalMonitor pattern applied to {}", struct_name);
547
548 let expanded = quote! {
549 impl #struct_name {
550 pub fn monitor_operation<F, R>(&self, operation_name: &str, operation: F) -> R
552 where
553 F: FnOnce() -> R,
554 {
555 let start = std::time::Instant::now();
556 let result = operation();
557 let duration_ms = start.elapsed().as_millis() as u64;
558
559 tracing::info!(
560 entity = %stringify!(#struct_name),
561 operation = %operation_name,
562 duration_ms = %duration_ms,
563 "Operation monitored for architectural analysis"
564 );
565
566 result
567 }
568
569 pub fn analyze_architectural_patterns(&self) -> Vec<String> {
571 let mut patterns = Vec::new();
572
573 patterns.push(format!("DomainEntity: {}", stringify!(#struct_name)));
574
575 let type_name = stringify!(#struct_name).to_lowercase();
576 if type_name.contains("address") || type_name.contains("customer") {
577 patterns.push("BrazilianEntityPattern".to_string());
578 }
579
580 if type_name.contains("input") || type_name.contains("object") || type_name.contains("mutation") {
581 patterns.push("GraphQLPattern".to_string());
582 }
583
584 if type_name.contains("repository") || type_name.contains("service") {
585 patterns.push("RepositoryServicePattern".to_string());
586 }
587
588 tracing::debug!(
589 entity = %stringify!(#struct_name),
590 patterns = ?patterns,
591 "Architectural patterns analyzed"
592 );
593
594 patterns
595 }
596
597 pub fn generate_health_report(&self) -> serde_json::Value {
599 let patterns = self.analyze_architectural_patterns();
600
601 serde_json::json!({
602 "entity": stringify!(#struct_name),
603 "detected_patterns": patterns,
604 "health_score": self.calculate_health_score(),
605 "recommendations": self.get_architectural_recommendations(),
606 "timestamp": chrono::Utc::now().to_rfc3339()
607 })
608 }
609
610 fn calculate_health_score(&self) -> f64 {
612 let patterns = self.analyze_architectural_patterns();
613 let pattern_count = patterns.len() as f64;
614
615 let pattern_score = (pattern_count / 5.0).min(1.0);
616 let type_name = stringify!(#struct_name);
617 let naming_score = if type_name.chars().next().unwrap().is_uppercase() { 0.2 } else { 0.0 };
618
619 (pattern_score + naming_score).min(1.0)
620 }
621
622 fn get_architectural_recommendations(&self) -> Vec<String> {
624 let mut recommendations = Vec::new();
625 let patterns = self.analyze_architectural_patterns();
626
627 if !patterns.iter().any(|p| p.contains("DomainModel")) {
628 recommendations.push("Consider adding DomainModel derive macro".to_string());
629 }
630
631 if !patterns.iter().any(|p| p.contains("GraphQL")) {
632 recommendations.push("Consider adding GraphQLBridge if this entity is exposed via GraphQL".to_string());
633 }
634
635 let type_name = stringify!(#struct_name).to_lowercase();
636 if type_name.contains("address") || type_name.contains("customer") {
637 if !patterns.iter().any(|p| p.contains("Brazilian")) {
638 recommendations.push("Consider adding BrazilianEntity derive macro for market-specific features".to_string());
639 }
640 }
641
642 recommendations
643 }
644 }
645 };
646
647 TokenStream::from(expanded)
648}
649
650#[proc_macro_derive(StatusStateMachine, attributes(status))]
652pub fn derive_status_state_machine(input: TokenStream) -> TokenStream {
653 status_patterns::derive_status_state_machine(input)
654}
655
656#[proc_macro_derive(BrazilianTaxEntity, attributes(tax))]
658pub fn derive_brazilian_tax_entity(input: TokenStream) -> TokenStream {
659 brazilian_patterns::derive_brazilian_tax_entity(input)
660}
661
662#[proc_macro_derive(ShippingEntity, attributes(shipping))]
664pub fn derive_shipping_entity(input: TokenStream) -> TokenStream {
665 brazilian_patterns::derive_shipping_entity(input)
666}
667
668#[proc_macro_derive(ValidatedEntity, attributes(validate))]
670pub fn derive_validated_entity(input: TokenStream) -> TokenStream {
671 validation_patterns::derive_validated_entity(input)
672}
673
674#[proc_macro_derive(IdentifierEntity, attributes(identifier))]
676pub fn derive_identifier_entity(input: TokenStream) -> TokenStream {
677 identifier_patterns::derive_identifier_entity(input)
678}
679
680#[proc_macro_derive(PaymentEntity, attributes(payment))]
686pub fn derive_payment_entity(input: TokenStream) -> TokenStream {
687 payment_patterns::derive_payment_entity(input)
688}
689
690#[proc_macro_derive(PixPayment, attributes(pix))]
692pub fn derive_pix_payment(input: TokenStream) -> TokenStream {
693 payment_patterns::derive_pix_payment(input)
694}
695
696#[proc_macro_derive(WalletEntity, attributes(wallet))]
698pub fn derive_wallet_entity(input: TokenStream) -> TokenStream {
699 wallet_patterns::derive_wallet_entity(input)
700}
701
702#[proc_macro_derive(RowMapper, attributes(row))]
704pub fn derive_row_mapper(input: TokenStream) -> TokenStream {
705 repository_helpers::derive_row_mapper(input)
706}
707
708#[proc_macro_derive(RepositoryCrud, attributes(repository))]
710pub fn derive_repository_crud(input: TokenStream) -> TokenStream {
711 repository_helpers::derive_repository_crud(input)
712}
713
714#[proc_macro_derive(SubscriptionEntity, attributes(subscription))]
716pub fn derive_subscription_entity(input: TokenStream) -> TokenStream {
717 subscription_patterns::derive_subscription_entity(input)
718}
719
720