use pleme_codegen::*;
use syn::{parse_quote, DeriveInput, Attribute};
use quote::{quote, ToTokens};
use proc_macro2::TokenStream;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
enum PaymentStatus {
Pending,
Processing,
Completed,
Failed,
Refunded,
Cancelled,
}
impl PaymentStatus {
fn as_str(&self) -> &'static str {
match self {
PaymentStatus::Pending => "pending",
PaymentStatus::Processing => "processing",
PaymentStatus::Completed => "completed",
PaymentStatus::Failed => "failed",
PaymentStatus::Refunded => "refunded",
PaymentStatus::Cancelled => "cancelled",
}
}
}
impl FromStr for PaymentStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"pending" => Ok(PaymentStatus::Pending),
"processing" => Ok(PaymentStatus::Processing),
"completed" => Ok(PaymentStatus::Completed),
"failed" => Ok(PaymentStatus::Failed),
"refunded" => Ok(PaymentStatus::Refunded),
"cancelled" => Ok(PaymentStatus::Cancelled),
_ => Err(format!("Invalid payment status: {}", s)),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum PaymentError {
#[error("Invalid amount")]
InvalidAmount,
#[error("Invalid state transition from {from:?} to {to:?}")]
InvalidStateTransition { from: PaymentStatus, to: PaymentStatus },
#[error("Amount too low: minimum {min}, got {actual}")]
AmountTooLow { min: rust_decimal::Decimal, actual: rust_decimal::Decimal },
#[error("Amount too high: maximum {max}, got {actual}")]
AmountTooHigh { max: rust_decimal::Decimal, actual: rust_decimal::Decimal },
#[error("QR code generation failed: {reason}")]
QrCodeGenerationFailed { reason: String },
#[error("Transaction failed: {0}")]
TransactionFailed(String),
#[error("Validation failed: {0}")]
ValidationFailed(String),
}
mod level_0_tests {
use super::*;
#[cfg(test)]
mod config_tests {
use super::*;
#[test]
fn test_payment_entity_config_parsing() {
let attrs: Vec<Attribute> = vec![
parse_quote!(#[payment(currency = "BRL", validation = "strict")])
];
let config = MockPaymentConfig {
currency: "BRL".to_string(),
validation: "strict".to_string(),
level: ArchitecturalLevel::Level0,
side_effects: false,
};
assert_eq!(config.currency, "BRL");
assert_eq!(config.validation, "strict");
assert_eq!(config.level, ArchitecturalLevel::Level0);
assert!(!config.side_effects); }
#[test]
#[should_panic(expected = "Level 0 cannot have side effects")]
fn test_hierarchy_violation_detection() {
let config = MockPaymentConfig {
currency: "BRL".to_string(),
validation: "strict".to_string(),
level: ArchitecturalLevel::Level0,
side_effects: true, };
config.validate_hierarchy().expect("Should panic on hierarchy violation");
}
#[test]
fn test_brazilian_market_config() {
let attrs: Vec<Attribute> = vec![
parse_quote!(#[payment(currency = "BRL", market = "brazil", pix = true)])
];
let config = MockPaymentConfig {
currency: "BRL".to_string(),
validation: "strict".to_string(),
level: ArchitecturalLevel::Level0,
side_effects: false,
};
assert_eq!(config.currency, "BRL");
assert!(config.supports_pix());
assert!(config.supports_cpf_validation());
}
#[test]
fn test_error_handling_compliance() {
let config = MockPaymentConfig::default();
assert!(config.enforces_result_types());
assert!(config.uses_service_error_integration());
}
#[derive(Debug, Clone)]
struct MockPaymentConfig {
currency: String,
validation: String,
level: ArchitecturalLevel,
side_effects: bool,
}
impl Default for MockPaymentConfig {
fn default() -> Self {
Self {
currency: "BRL".to_string(),
validation: "strict".to_string(),
level: ArchitecturalLevel::Level0,
side_effects: false,
}
}
}
impl MockPaymentConfig {
fn validate_hierarchy(&self) -> Result<(), String> {
match self.level {
ArchitecturalLevel::Level0 => {
if self.side_effects {
panic!("Level 0 cannot have side effects");
}
}
_ => {}
}
Ok(())
}
fn supports_pix(&self) -> bool {
self.currency == "BRL"
}
fn supports_cpf_validation(&self) -> bool {
self.currency == "BRL"
}
fn enforces_result_types(&self) -> bool {
true }
fn uses_service_error_integration(&self) -> bool {
true }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ArchitecturalLevel {
Level0,
Level1,
Level2,
Level3,
}
}
#[cfg(test)]
mod generation_tests {
use super::*;
#[test]
fn test_payment_entity_generation() {
let input: DeriveInput = parse_quote! {
#[derive(PaymentEntity)]
#[payment(currency = "BRL")]
pub struct Payment {
pub amount: rust_decimal::Decimal,
pub status: PaymentStatus,
pub user_id: uuid::Uuid,
}
};
let generated_code = mock_generate_payment_entity(&input);
assert!(generated_code.contains("fn mark_processing"));
assert!(generated_code.contains("fn mark_completed"));
assert!(generated_code.contains("fn total_amount"));
assert!(generated_code.contains("fn validate_amount"));
assert!(generated_code.contains("Result<"));
assert!(!generated_code.contains("async fn"));
assert!(!generated_code.contains("database"));
assert!(!generated_code.contains("redis"));
assert!(!generated_code.contains("http"));
assert!(generated_code.contains("#[cfg(test)]"));
assert!(generated_code.contains("fn create_test_payment"));
assert!(generated_code.contains("ArchitecturalHealth"));
assert!(generated_code.contains("Level0"));
assert!(generated_code.contains("BRL"));
assert!(generated_code.contains("calculate_tax"));
}
#[test]
fn test_pix_payment_generation() {
let input: DeriveInput = parse_quote! {
#[derive(PixPayment)]
#[pix(qr_size = 256)]
pub struct PixPayment {
pub pix_key: String,
pub amount: rust_decimal::Decimal,
}
};
let generated_code = mock_generate_pix_payment(&input);
assert!(generated_code.contains("fn generate_qr_payload"));
assert!(generated_code.contains("fn generate_qr_code_image"));
assert!(generated_code.contains("fn validate_pix_key"));
assert!(generated_code.contains("fn is_expired"));
assert!(generated_code.contains("CPF"));
assert!(generated_code.contains("CNPJ"));
assert!(generated_code.contains("BRL"));
assert!(!generated_code.contains("async fn"));
}
#[test]
fn test_wallet_entity_generation() {
let input: DeriveInput = parse_quote! {
#[derive(WalletEntity)]
#[wallet(currency = "BRL")]
pub struct Wallet {
pub balance: rust_decimal::Decimal,
pub tokens: u64,
pub locked: bool,
}
};
let generated_code = mock_generate_wallet_entity(&input);
assert!(generated_code.contains("fn add_balance"));
assert!(generated_code.contains("fn subtract_balance"));
assert!(generated_code.contains("fn add_tokens"));
assert!(generated_code.contains("fn spend_tokens"));
assert!(generated_code.contains("fn is_locked"));
assert!(!generated_code.contains("async fn"));
assert!(generated_code.contains("Result<"));
}
#[test]
fn test_row_mapper_generation() {
let input: DeriveInput = parse_quote! {
#[derive(RowMapper)]
pub struct Payment {
pub id: uuid::Uuid,
pub amount: rust_decimal::Decimal,
pub status: PaymentStatus,
}
};
let generated_code = mock_generate_row_mapper(&input);
assert!(generated_code.contains("fn from_row"));
assert!(generated_code.contains("fn from_rows"));
assert!(generated_code.contains("fn from_optional_row"));
assert!(generated_code.contains("BigDecimal"));
assert!(generated_code.contains("Decimal::from_str"));
assert!(generated_code.contains("try_get"));
}
fn mock_generate_payment_entity(_input: &DeriveInput) -> String {
r#"
impl Payment {
fn mark_processing(&mut self) -> Result<(), PaymentError> { Ok(()) }
fn mark_completed(&mut self) -> Result<(), PaymentError> { Ok(()) }
fn mark_failed(&mut self, reason: String) -> Result<(), PaymentError> { Ok(()) }
fn total_amount(&self) -> rust_decimal::Decimal { self.amount }
fn validate_amount(&self) -> Result<(), PaymentError> { Ok(()) }
fn calculate_tax(&self) -> rust_decimal::Decimal {
self.amount * rust_decimal::Decimal::from_str("0.1").unwrap()
}
}
#[cfg(test)]
impl Payment {
fn create_test_payment() -> Self {
Self {
amount: rust_decimal::Decimal::from(100),
status: PaymentStatus::Pending,
user_id: uuid::Uuid::new_v4(),
}
}
}
impl ArchitecturalHealth for Payment {
fn architectural_level() -> ArchitecturalLevel { ArchitecturalLevel::Level0 }
fn has_side_effects() -> bool { false }
fn quality_score() -> f64 { 0.95 }
}
"#.to_string()
}
fn mock_generate_pix_payment(_input: &DeriveInput) -> String {
r#"
impl PixPayment {
fn generate_qr_payload(&self) -> Result<String, PaymentError> { Ok("payload".to_string()) }
fn generate_qr_code_image(&self) -> Result<Vec<u8>, PaymentError> { Ok(vec![]) }
fn validate_pix_key(&self) -> Result<(), PaymentError> {
if self.pix_key.contains("@") { Ok(()) }
else if self.pix_key.len() == 11 { Ok(()) } // CPF
else if self.pix_key.len() == 14 { Ok(()) } // CNPJ
else { Err(PaymentError::ValidationFailed("Invalid PIX key".to_string())) }
}
fn is_expired(&self) -> bool { false }
}
"#.to_string()
}
fn mock_generate_wallet_entity(_input: &DeriveInput) -> String {
r#"
impl Wallet {
fn add_balance(&mut self, amount: rust_decimal::Decimal) -> Result<(), PaymentError> {
if amount > rust_decimal::Decimal::ZERO {
self.balance += amount;
Ok(())
} else {
Err(PaymentError::InvalidAmount)
}
}
fn subtract_balance(&mut self, amount: rust_decimal::Decimal) -> Result<(), PaymentError> {
if self.balance >= amount {
self.balance -= amount;
Ok(())
} else {
Err(PaymentError::InvalidAmount)
}
}
fn add_tokens(&mut self, tokens: u64) -> Result<(), PaymentError> {
self.tokens += tokens;
Ok(())
}
fn spend_tokens(&mut self, tokens: u64) -> Result<(), PaymentError> {
if self.tokens >= tokens {
self.tokens -= tokens;
Ok(())
} else {
Err(PaymentError::InvalidAmount)
}
}
fn is_locked(&self) -> bool { self.locked }
}
"#.to_string()
}
fn mock_generate_row_mapper(_input: &DeriveInput) -> String {
r#"
impl Payment {
fn from_row(row: &sqlx::Row) -> Result<Self, sqlx::Error> {
use sqlx::Row;
use std::str::FromStr;
Ok(Self {
id: row.try_get("id")?,
amount: rust_decimal::Decimal::from_str(
&row.try_get::<sqlx::types::BigDecimal, _>("amount")?.to_string()
).map_err(|e| sqlx::Error::ColumnDecode {
index: "amount".to_string(),
source: Box::new(e)
})?,
status: row.try_get::<String, _>("status")?.parse()
.map_err(|e| sqlx::Error::ColumnDecode {
index: "status".to_string(),
source: Box::new(e)
})?,
})
}
fn from_rows(rows: Vec<sqlx::Row>) -> Result<Vec<Self>, sqlx::Error> {
rows.into_iter().map(|row| Self::from_row(&row)).collect()
}
fn from_optional_row(row: Option<sqlx::Row>) -> Result<Option<Self>, sqlx::Error> {
row.map(|r| Self::from_row(&r)).transpose()
}
}
"#.to_string()
}
trait ArchitecturalHealth {
fn architectural_level() -> ArchitecturalLevel;
fn has_side_effects() -> bool;
fn quality_score() -> f64;
}
}
}
mod level_1_tests {
use super::*;
#[cfg(test)]
mod repository_tests {
use super::*;
#[test]
fn test_repository_crud_generation() {
let input: DeriveInput = parse_quote! {
#[derive(RepositoryCrud)]
#[repository(entity = "Payment", cache_ttl = 300)]
pub struct PaymentRepository {
pool: sqlx::PgPool,
redis: Option<deadpool_redis::Pool>,
}
};
let generated_code = mock_generate_repository_crud(&input);
assert!(generated_code.contains("async fn create_with_cache"));
assert!(generated_code.contains("async fn find_by_id_cached"));
assert!(generated_code.contains("async fn update_with_cache"));
assert!(generated_code.contains("async fn delete_with_cache"));
assert!(generated_code.contains("invalidate_cache"));
assert!(generated_code.contains("warm_cache"));
assert!(generated_code.contains("Result<Payment, PaymentError>"));
assert!(generated_code.contains("ServiceError"));
assert!(!generated_code.contains("validate_payment"));
assert!(!generated_code.contains("calculate_fees"));
assert!(!generated_code.contains("process_payment"));
assert!(generated_code.contains("sqlx::query("));
assert!(generated_code.contains(".bind("));
assert!(!generated_code.contains("sqlx::query!"));
}
fn mock_generate_repository_crud(_input: &DeriveInput) -> String {
r#"
impl PaymentRepository {
async fn create_with_cache(&self, payment: &Payment) -> Result<Payment, PaymentError> {
// Manual sqlx query (no macros)
let row = sqlx::query(
"INSERT INTO payments (id, amount, status) VALUES ($1, $2, $3) RETURNING *"
)
.bind(payment.id)
.bind(payment.amount)
.bind(payment.status.as_str())
.fetch_one(&self.pool)
.await
.map_err(|e| PaymentError::TransactionFailed(e.to_string()))?;
let created = Payment::from_row(&row)?;
// Cache the result
if let Some(ref redis) = self.redis {
let _ = self.warm_cache(&created).await;
}
Ok(created)
}
async fn find_by_id_cached(&self, id: uuid::Uuid, product: &str) -> Result<Option<Payment>, PaymentError> {
// Try cache first
if let Some(ref redis) = self.redis {
if let Ok(Some(cached)) = self.get_cached_payment(id, product).await {
return Ok(Some(cached));
}
}
// Query database
let result = sqlx::query("SELECT * FROM payments WHERE id = $1 AND product = $2")
.bind(id)
.bind(product)
.fetch_optional(&self.pool)
.await
.map_err(|e| PaymentError::TransactionFailed(e.to_string()))?;
match result {
Some(row) => {
let payment = Payment::from_row(&row)?;
let _ = self.warm_cache(&payment).await;
Ok(Some(payment))
}
None => Ok(None)
}
}
async fn invalidate_cache(&self, id: uuid::Uuid, product: &str) -> Result<(), PaymentError> {
if let Some(ref redis) = self.redis {
let key = format!("pay:{}:{}", product, id);
let mut conn = redis.get().await
.map_err(|e| PaymentError::TransactionFailed(e.to_string()))?;
use redis::AsyncCommands;
let _: () = conn.del(&key).await
.map_err(|e| PaymentError::TransactionFailed(e.to_string()))?;
}
Ok(())
}
async fn warm_cache(&self, payment: &Payment) -> Result<(), PaymentError> {
// Cache implementation
Ok(())
}
async fn get_cached_payment(&self, id: uuid::Uuid, product: &str) -> Result<Option<Payment>, PaymentError> {
// Cache retrieval implementation
Ok(None)
}
}
"#.to_string()
}
}
}
mod integration_tests {
use super::*;
#[tokio::test]
async fn test_full_payment_workflow() {
let mut payment = MockPayment::new(
rust_decimal::Decimal::from(100),
PaymentStatus::Pending,
uuid::Uuid::new_v4(),
);
assert_eq!(payment.status, PaymentStatus::Pending);
assert!(payment.can_transition_to(PaymentStatus::Processing));
payment.mark_processing().unwrap();
assert_eq!(payment.status, PaymentStatus::Processing);
payment.mark_completed().unwrap();
assert_eq!(payment.status, PaymentStatus::Completed);
assert!(payment.is_refundable());
assert_eq!(payment.total_amount(), rust_decimal::Decimal::from(100));
assert!(payment.validate().is_ok());
assert!(payment.validate_amount().is_ok());
}
#[tokio::test]
async fn test_architectural_compliance() {
assert_eq!(MockPayment::architectural_level(), ArchitecturalLevel::Level0);
assert!(!MockPayment::has_side_effects());
assert!(MockPayment::quality_score() > 0.8);
assert_eq!(MockPaymentRepository::architectural_level(), ArchitecturalLevel::Level1);
assert!(MockPaymentRepository::has_side_effects()); assert!(MockPaymentRepository::quality_score() > 0.8);
}
#[tokio::test]
async fn test_error_handling_compliance() {
let mut payment = MockPayment::new(
rust_decimal::Decimal::from(-100), PaymentStatus::Pending,
uuid::Uuid::new_v4(),
);
let result = payment.validate_amount();
assert!(result.is_err());
match result.unwrap_err() {
PaymentError::InvalidAmount => {
}
_ => panic!("Expected InvalidAmount error"),
}
}
struct MockPayment {
amount: rust_decimal::Decimal,
status: PaymentStatus,
user_id: uuid::Uuid,
}
impl MockPayment {
fn new(amount: rust_decimal::Decimal, status: PaymentStatus, user_id: uuid::Uuid) -> Self {
Self { amount, status, user_id }
}
fn mark_processing(&mut self) -> Result<(), PaymentError> {
if self.status == PaymentStatus::Pending {
self.status = PaymentStatus::Processing;
Ok(())
} else {
Err(PaymentError::InvalidStateTransition {
from: self.status.clone(),
to: PaymentStatus::Processing,
})
}
}
fn mark_completed(&mut self) -> Result<(), PaymentError> {
if self.status == PaymentStatus::Processing {
self.status = PaymentStatus::Completed;
Ok(())
} else {
Err(PaymentError::InvalidStateTransition {
from: self.status.clone(),
to: PaymentStatus::Completed,
})
}
}
fn can_transition_to(&self, status: PaymentStatus) -> bool {
match (&self.status, status) {
(PaymentStatus::Pending, PaymentStatus::Processing) => true,
(PaymentStatus::Processing, PaymentStatus::Completed) => true,
(PaymentStatus::Processing, PaymentStatus::Failed) => true,
(PaymentStatus::Completed, PaymentStatus::Refunded) => true,
_ => false,
}
}
fn is_refundable(&self) -> bool {
matches!(self.status, PaymentStatus::Completed)
}
fn total_amount(&self) -> rust_decimal::Decimal {
self.amount
}
fn validate(&self) -> Result<(), PaymentError> {
self.validate_amount()?;
Ok(())
}
fn validate_amount(&self) -> Result<(), PaymentError> {
if self.amount <= rust_decimal::Decimal::ZERO {
Err(PaymentError::InvalidAmount)
} else {
Ok(())
}
}
fn architectural_level() -> ArchitecturalLevel {
ArchitecturalLevel::Level0
}
fn has_side_effects() -> bool {
false
}
fn quality_score() -> f64 {
0.95
}
}
struct MockPaymentRepository;
impl MockPaymentRepository {
fn architectural_level() -> ArchitecturalLevel {
ArchitecturalLevel::Level1
}
fn has_side_effects() -> bool {
true }
fn quality_score() -> f64 {
0.90
}
}
}
mod compliance_tests {
use super::*;
#[test]
fn test_edition2024_compatibility() {
let payment_tokens = mock_generate_payment_entity_tokens();
let parsed = syn::parse2::<syn::File>(payment_tokens.clone());
assert!(parsed.is_ok(), "Generated code must be valid Rust syntax");
let code = payment_tokens.to_string();
assert!(!code.contains("never_type"), "No deprecated never type fallback");
assert!(!code.contains("unsafe"), "No unsafe code in generated macros");
}
#[test]
fn test_dependency_injection_compliance() {
let repo_tokens = mock_generate_repository_tokens();
let service_tokens = mock_generate_service_tokens();
let repo_code = repo_tokens.to_string();
let service_code = service_tokens.to_string();
assert!(!repo_code.contains("process_payment"));
assert!(!repo_code.contains("validate_business_rules"));
assert!(service_code.contains("repository"));
assert!(service_code.contains("async fn"));
}
#[test]
fn test_brazilian_compliance() {
let pix_tokens = mock_generate_pix_tokens();
let code = pix_tokens.to_string();
assert!(code.contains("cpf"));
assert!(code.contains("cnpj"));
assert!(code.contains("pix_key"));
assert!(code.contains("BRL"));
assert!(code.contains("validate_cpf"));
assert!(code.contains("validate_cnpj"));
}
#[test]
fn test_quality_gates() {
let payment_tokens = mock_generate_payment_entity_tokens();
let code = payment_tokens.to_string();
assert!(code.contains("Result<"), "All methods must return Result");
assert!(code.contains("PaymentError"), "Must use proper error types");
assert!(code.contains("#[cfg(test)]"), "Must generate test helpers");
assert!(!code.contains("unwrap()"), "No direct unwrapping in generated code");
assert!(!code.contains("panic!"), "No panics in generated code");
assert!(!code.contains(".expect(\"unwrap"), "Use Result propagation instead of expect");
}
fn mock_generate_payment_entity_tokens() -> TokenStream {
quote! {
impl Payment {
fn mark_processing(&mut self) -> Result<(), PaymentError> {
Ok(())
}
fn validate_amount(&self) -> Result<(), PaymentError> {
if self.amount <= rust_decimal::Decimal::ZERO {
return Err(PaymentError::InvalidAmount);
}
Ok(())
}
}
#[cfg(test)]
impl Payment {
fn create_test_payment() -> Self {
Self::default()
}
}
}
}
fn mock_generate_repository_tokens() -> TokenStream {
quote! {
impl PaymentRepository {
async fn create_with_cache(&self, payment: &Payment) -> Result<Payment, PaymentError> {
let row = sqlx::query("INSERT INTO payments (id, amount) VALUES ($1, $2)")
.bind(payment.id)
.bind(payment.amount)
.fetch_one(&self.pool)
.await?;
Ok(Payment::from_row(&row)?)
}
}
}
}
fn mock_generate_service_tokens() -> TokenStream {
quote! {
impl PaymentService {
async fn process_payment(&self, request: ProcessPaymentRequest) -> Result<Payment, PaymentError> {
let payment = Payment::from_request(request)?;
let validated = payment.validate()?;
self.repository.create_with_cache(&validated).await
}
}
}
}
fn mock_generate_pix_tokens() -> TokenStream {
quote! {
impl PixPayment {
fn validate_pix_key(&self) -> Result<(), PaymentError> {
if self.pix_key.contains("@") {
Ok(()) } else if self.pix_key.len() == 11 {
self.validate_cpf(&self.pix_key)
} else if self.pix_key.len() == 14 {
self.validate_cnpj(&self.pix_key)
} else {
Err(PaymentError::ValidationFailed("Invalid PIX key format".to_string()))
}
}
fn validate_cpf(&self, cpf: &str) -> Result<(), PaymentError> {
Ok(())
}
fn validate_cnpj(&self, cnpj: &str) -> Result<(), PaymentError> {
Ok(())
}
fn generate_qr_code(&self) -> Result<String, PaymentError> {
Ok(format!("pix://pay/{}?amount={}", self.pix_key, self.amount))
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ArchitecturalLevel {
Level0,
Level1,
Level2,
Level3,
}