use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
use rust_decimal::Decimal;
use std::str::FromStr;
use uuid::Uuid;
#[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")]
InvalidStateTransition,
#[error("Validation failed: {0}")]
ValidationFailed(String),
}
#[derive(Debug, Clone)]
struct GeneratedPayment {
pub id: Uuid,
pub amount: Decimal,
pub status: PaymentStatus,
pub user_id: Uuid,
}
impl GeneratedPayment {
fn new(amount: Decimal, status: PaymentStatus, user_id: Uuid) -> Self {
Self {
id: Uuid::new_v4(),
amount,
status,
user_id,
}
}
fn mark_processing(&mut self) -> Result<(), PaymentError> {
match self.status {
PaymentStatus::Pending => {
self.status = PaymentStatus::Processing;
Ok(())
}
_ => Err(PaymentError::InvalidStateTransition),
}
}
fn mark_completed(&mut self) -> Result<(), PaymentError> {
match self.status {
PaymentStatus::Processing => {
self.status = PaymentStatus::Completed;
Ok(())
}
_ => Err(PaymentError::InvalidStateTransition),
}
}
fn total_amount(&self) -> Decimal {
self.amount + (self.amount * Decimal::from_str("0.1").unwrap())
}
fn validate_amount(&self) -> Result<(), PaymentError> {
if self.amount <= Decimal::ZERO {
Err(PaymentError::InvalidAmount)
} else {
Ok(())
}
}
fn is_refundable(&self) -> bool {
matches!(self.status, PaymentStatus::Completed)
}
fn validate(&self) -> Result<(), PaymentError> {
self.validate_amount()?;
Ok(())
}
fn can_transition_to(&self, new_status: PaymentStatus) -> bool {
match (&self.status, new_status) {
(PaymentStatus::Pending, PaymentStatus::Processing) => true,
(PaymentStatus::Processing, PaymentStatus::Completed) => true,
(PaymentStatus::Processing, PaymentStatus::Failed) => true,
(PaymentStatus::Completed, PaymentStatus::Refunded) => true,
_ => false,
}
}
fn calculate_brazilian_tax(&self) -> Decimal {
let icms = self.amount * Decimal::from_str("0.18").unwrap(); let pis = self.amount * Decimal::from_str("0.0165").unwrap(); let cofins = self.amount * Decimal::from_str("0.076").unwrap(); icms + pis + cofins
}
}
#[derive(Debug, Clone)]
struct ManualPayment {
pub id: Uuid,
pub amount: Decimal,
pub status: PaymentStatus,
pub user_id: Uuid,
}
impl ManualPayment {
fn new(amount: Decimal, status: PaymentStatus, user_id: Uuid) -> Self {
Self {
id: Uuid::new_v4(),
amount,
status,
user_id,
}
}
fn mark_processing(&mut self) -> Result<(), PaymentError> {
match self.status {
PaymentStatus::Pending => {
self.status = PaymentStatus::Processing;
Ok(())
}
_ => Err(PaymentError::InvalidStateTransition),
}
}
fn mark_completed(&mut self) -> Result<(), PaymentError> {
match self.status {
PaymentStatus::Processing => {
self.status = PaymentStatus::Completed;
Ok(())
}
_ => Err(PaymentError::InvalidStateTransition),
}
}
fn total_amount(&self) -> Decimal {
self.amount + (self.amount * Decimal::from_str("0.1").unwrap())
}
fn validate_amount(&self) -> Result<(), PaymentError> {
if self.amount <= Decimal::ZERO {
Err(PaymentError::InvalidAmount)
} else {
Ok(())
}
}
fn is_refundable(&self) -> bool {
matches!(self.status, PaymentStatus::Completed)
}
fn validate(&self) -> Result<(), PaymentError> {
self.validate_amount()?;
Ok(())
}
fn can_transition_to(&self, new_status: PaymentStatus) -> bool {
match (&self.status, new_status) {
(PaymentStatus::Pending, PaymentStatus::Processing) => true,
(PaymentStatus::Processing, PaymentStatus::Completed) => true,
(PaymentStatus::Processing, PaymentStatus::Failed) => true,
(PaymentStatus::Completed, PaymentStatus::Refunded) => true,
_ => false,
}
}
fn calculate_brazilian_tax(&self) -> Decimal {
let icms = self.amount * Decimal::from_str("0.18").unwrap();
let pis = self.amount * Decimal::from_str("0.0165").unwrap();
let cofins = self.amount * Decimal::from_str("0.076").unwrap();
icms + pis + cofins
}
}
#[derive(Debug, Clone)]
struct GeneratedPixPayment {
pub id: Uuid,
pub payment_id: Uuid,
pub pix_key: String,
pub amount: Decimal,
pub qr_code: String,
}
impl GeneratedPixPayment {
fn new(payment_id: Uuid, pix_key: String, amount: Decimal) -> Self {
Self {
id: Uuid::new_v4(),
payment_id,
pix_key,
amount,
qr_code: String::new(),
}
}
fn validate_pix_key(&self) -> Result<(), PaymentError> {
if self.pix_key.contains("@") {
if self.pix_key.len() > 3 && self.pix_key.contains(".") {
Ok(())
} else {
Err(PaymentError::ValidationFailed("Invalid email".to_string()))
}
} else if self.pix_key.len() == 11 {
self.validate_cpf()
} else if self.pix_key.len() == 14 {
self.validate_cnpj()
} else {
Err(PaymentError::ValidationFailed("Invalid PIX key format".to_string()))
}
}
fn validate_cpf(&self) -> Result<(), PaymentError> {
let digits: String = self.pix_key.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() == 11 {
Ok(())
} else {
Err(PaymentError::ValidationFailed("Invalid CPF".to_string()))
}
}
fn validate_cnpj(&self) -> Result<(), PaymentError> {
let digits: String = self.pix_key.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() == 14 {
Ok(())
} else {
Err(PaymentError::ValidationFailed("Invalid CNPJ".to_string()))
}
}
fn generate_qr_payload(&self) -> Result<String, PaymentError> {
Ok(format!(
"PIX|{}|{}|BRL|{}",
self.pix_key,
self.amount,
self.payment_id
))
}
fn is_expired(&self) -> bool {
false
}
}
#[derive(Debug, Clone)]
struct ManualPixPayment {
pub id: Uuid,
pub payment_id: Uuid,
pub pix_key: String,
pub amount: Decimal,
pub qr_code: String,
}
impl ManualPixPayment {
fn new(payment_id: Uuid, pix_key: String, amount: Decimal) -> Self {
Self {
id: Uuid::new_v4(),
payment_id,
pix_key,
amount,
qr_code: String::new(),
}
}
fn validate_pix_key(&self) -> Result<(), PaymentError> {
if self.pix_key.contains("@") {
if self.pix_key.len() > 3 && self.pix_key.contains(".") {
Ok(())
} else {
Err(PaymentError::ValidationFailed("Invalid email".to_string()))
}
} else if self.pix_key.len() == 11 {
self.validate_cpf()
} else if self.pix_key.len() == 14 {
self.validate_cnpj()
} else {
Err(PaymentError::ValidationFailed("Invalid PIX key format".to_string()))
}
}
fn validate_cpf(&self) -> Result<(), PaymentError> {
let digits: String = self.pix_key.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() == 11 {
Ok(())
} else {
Err(PaymentError::ValidationFailed("Invalid CPF".to_string()))
}
}
fn validate_cnpj(&self) -> Result<(), PaymentError> {
let digits: String = self.pix_key.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() == 14 {
Ok(())
} else {
Err(PaymentError::ValidationFailed("Invalid CNPJ".to_string()))
}
}
fn generate_qr_payload(&self) -> Result<String, PaymentError> {
Ok(format!(
"PIX|{}|{}|BRL|{}",
self.pix_key,
self.amount,
self.payment_id
))
}
fn is_expired(&self) -> bool {
false
}
}
fn benchmark_payment_state_transitions(c: &mut Criterion) {
let mut group = c.benchmark_group("payment_state_transitions");
for amount in [100, 1000, 10000].iter() {
let amount_decimal = Decimal::from(*amount);
group.bench_with_input(
BenchmarkId::new("generated_mark_processing", amount),
&amount_decimal,
|b, &amount| {
b.iter(|| {
let mut payment = GeneratedPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.mark_processing())
})
},
);
group.bench_with_input(
BenchmarkId::new("manual_mark_processing", amount),
&amount_decimal,
|b, &amount| {
b.iter(|| {
let mut payment = ManualPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.mark_processing())
})
},
);
}
group.finish();
}
fn benchmark_payment_validation(c: &mut Criterion) {
let mut group = c.benchmark_group("payment_validation");
let amounts = vec![
Decimal::from_str("0.01").unwrap(),
Decimal::from_str("100.50").unwrap(),
Decimal::from_str("10000.99").unwrap(),
];
for (i, amount) in amounts.iter().enumerate() {
group.bench_with_input(
BenchmarkId::new("generated_validate", i),
amount,
|b, &amount| {
b.iter(|| {
let payment = GeneratedPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.validate())
})
},
);
group.bench_with_input(
BenchmarkId::new("manual_validate", i),
amount,
|b, &amount| {
b.iter(|| {
let payment = ManualPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.validate())
})
},
);
}
group.finish();
}
fn benchmark_payment_calculations(c: &mut Criterion) {
let mut group = c.benchmark_group("payment_calculations");
for amount in [100, 1000, 10000].iter() {
let amount_decimal = Decimal::from(*amount);
group.bench_with_input(
BenchmarkId::new("generated_total_amount", amount),
&amount_decimal,
|b, &amount| {
b.iter(|| {
let payment = GeneratedPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.total_amount())
})
},
);
group.bench_with_input(
BenchmarkId::new("manual_total_amount", amount),
&amount_decimal,
|b, &amount| {
b.iter(|| {
let payment = ManualPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.total_amount())
})
},
);
group.bench_with_input(
BenchmarkId::new("generated_brazilian_tax", amount),
&amount_decimal,
|b, &amount| {
b.iter(|| {
let payment = GeneratedPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.calculate_brazilian_tax())
})
},
);
group.bench_with_input(
BenchmarkId::new("manual_brazilian_tax", amount),
&amount_decimal,
|b, &amount| {
b.iter(|| {
let payment = ManualPayment::new(
amount,
PaymentStatus::Pending,
Uuid::new_v4(),
);
black_box(payment.calculate_brazilian_tax())
})
},
);
}
group.finish();
}
fn benchmark_pix_validation(c: &mut Criterion) {
let mut group = c.benchmark_group("pix_validation");
let test_cases = vec![
("email", "user@example.com"),
("cpf", "12345678901"),
("cnpj", "12345678000190"),
("invalid", "invalid_key"),
];
for (case_name, pix_key) in test_cases.iter() {
group.bench_with_input(
BenchmarkId::new("generated_pix_validation", case_name),
pix_key,
|b, &pix_key| {
b.iter(|| {
let pix = GeneratedPixPayment::new(
Uuid::new_v4(),
pix_key.to_string(),
Decimal::from(100),
);
black_box(pix.validate_pix_key())
})
},
);
group.bench_with_input(
BenchmarkId::new("manual_pix_validation", case_name),
pix_key,
|b, &pix_key| {
b.iter(|| {
let pix = ManualPixPayment::new(
Uuid::new_v4(),
pix_key.to_string(),
Decimal::from(100),
);
black_box(pix.validate_pix_key())
})
},
);
}
group.finish();
}
fn benchmark_pix_qr_generation(c: &mut Criterion) {
let mut group = c.benchmark_group("pix_qr_generation");
let pix_key = "user@example.com";
let amount = Decimal::from(100);
group.bench_function("generated_qr_payload", |b| {
b.iter(|| {
let pix = GeneratedPixPayment::new(
Uuid::new_v4(),
pix_key.to_string(),
amount,
);
black_box(pix.generate_qr_payload())
})
});
group.bench_function("manual_qr_payload", |b| {
b.iter(|| {
let pix = ManualPixPayment::new(
Uuid::new_v4(),
pix_key.to_string(),
amount,
);
black_box(pix.generate_qr_payload())
})
});
group.finish();
}
fn benchmark_bulk_operations(c: &mut Criterion) {
let mut group = c.benchmark_group("bulk_operations");
let batch_sizes = vec![100, 1000, 5000];
for batch_size in batch_sizes.iter() {
group.bench_with_input(
BenchmarkId::new("generated_bulk_validation", batch_size),
batch_size,
|b, &size| {
b.iter(|| {
let payments: Vec<_> = (0..size)
.map(|i| {
GeneratedPayment::new(
Decimal::from(i as i64 + 1),
PaymentStatus::Pending,
Uuid::new_v4(),
)
})
.collect();
let results: Result<Vec<_>, _> = payments
.iter()
.map(|p| p.validate())
.collect();
black_box(results)
})
},
);
group.bench_with_input(
BenchmarkId::new("manual_bulk_validation", batch_size),
batch_size,
|b, &size| {
b.iter(|| {
let payments: Vec<_> = (0..size)
.map(|i| {
ManualPayment::new(
Decimal::from(i as i64 + 1),
PaymentStatus::Pending,
Uuid::new_v4(),
)
})
.collect();
let results: Result<Vec<_>, _> = payments
.iter()
.map(|p| p.validate())
.collect();
black_box(results)
})
},
);
}
group.finish();
}
criterion_group!(
benches,
benchmark_payment_state_transitions,
benchmark_payment_validation,
benchmark_payment_calculations,
benchmark_pix_validation,
benchmark_pix_qr_generation,
benchmark_bulk_operations
);
criterion_main!(benches);