use stilltypes::financial::{CreditCardExt, CreditCardNumber, Iban, IbanExt};
use stilltypes::prelude::*;
use stillwater::validation::{ValidateAll, Validation};
#[derive(Debug)]
struct PaymentInput {
method: PaymentMethod,
}
#[derive(Debug)]
enum PaymentMethod {
Card { number: String, name: String },
BankTransfer { iban: String, bic: String },
}
#[derive(Debug)]
enum ValidPaymentMethod {
Card {
number: CreditCardNumber,
name: String,
},
BankTransfer {
iban: Iban,
bic: String,
},
}
fn validate_payment(input: PaymentInput) -> Validation<ValidPaymentMethod, Vec<DomainError>> {
match input.method {
PaymentMethod::Card { number, name } => {
Validation::from_result(CreditCardNumber::new(number).map_err(|e| vec![e]))
.map(|card| ValidPaymentMethod::Card { number: card, name })
}
PaymentMethod::BankTransfer { iban, bic } => {
Validation::from_result(Iban::new(iban).map_err(|e| vec![e]))
.map(|iban| ValidPaymentMethod::BankTransfer { iban, bic })
}
}
}
#[derive(Debug)]
struct CheckoutForm {
primary_card: String,
backup_card: Option<String>,
bank_account: Option<String>,
}
#[derive(Debug)]
struct ValidCheckout {
primary_card: CreditCardNumber,
backup_card: Option<CreditCardNumber>,
bank_account: Option<Iban>,
}
fn validate_checkout(form: CheckoutForm) -> Validation<ValidCheckout, Vec<DomainError>> {
let primary_v =
Validation::from_result(CreditCardNumber::new(form.primary_card).map_err(|e| vec![e]));
let backup_v: Validation<Option<CreditCardNumber>, Vec<DomainError>> = match form.backup_card {
Some(card) => {
Validation::from_result(CreditCardNumber::new(card).map_err(|e| vec![e])).map(Some)
}
None => Validation::Success(None),
};
let bank_v: Validation<Option<Iban>, Vec<DomainError>> = match form.bank_account {
Some(iban) => Validation::from_result(Iban::new(iban).map_err(|e| vec![e])).map(Some),
None => Validation::Success(None),
};
(primary_v, backup_v, bank_v)
.validate_all()
.map(|(primary_card, backup_card, bank_account)| ValidCheckout {
primary_card,
backup_card,
bank_account,
})
}
fn generate_receipt(checkout: &ValidCheckout) -> String {
let mut receipt = String::from("Payment Receipt\n");
receipt.push_str("===============\n");
receipt.push_str(&format!(
"Primary Card: {} ({})\n",
checkout.primary_card.masked(),
checkout.primary_card.last_four()
));
if let Some(ref backup) = checkout.backup_card {
receipt.push_str(&format!("Backup Card: {}\n", backup.masked()));
}
if let Some(ref iban) = checkout.bank_account {
receipt.push_str(&format!(
"Bank Account: {} ({})\n",
iban.masked(),
iban.country_code()
));
}
receipt
}
fn main() {
println!("Stilltypes Financial Validation Example");
println!("========================================\n");
println!("=== Valid Credit Card ===");
let card_payment = PaymentInput {
method: PaymentMethod::Card {
number: "4111111111111111".into(),
name: "John Doe".into(),
},
};
match validate_payment(card_payment) {
Validation::Success(ValidPaymentMethod::Card { number, name }) => {
println!("Payment method valid!");
println!(" Cardholder: {}", name);
println!(
" Card: {} (last 4: {})",
number.masked(),
number.last_four()
);
}
Validation::Success(_) => unreachable!(),
Validation::Failure(errors) => {
for err in errors {
println!(" Error: {}", err);
}
}
}
println!("\n=== Valid Bank Transfer ===");
let bank_payment = PaymentInput {
method: PaymentMethod::BankTransfer {
iban: "DE89370400440532013000".into(),
bic: "COBADEFFXXX".into(),
},
};
match validate_payment(bank_payment) {
Validation::Success(ValidPaymentMethod::BankTransfer { iban, bic }) => {
println!("Payment method valid!");
println!(
" IBAN: {} (country: {})",
iban.masked(),
iban.country_code()
);
println!(" BIC: {}", bic);
}
Validation::Success(_) => unreachable!(),
Validation::Failure(errors) => {
for err in errors {
println!(" Error: {}", err);
}
}
}
println!("\n=== Invalid Credit Card (Luhn failure) ===");
let bad_card = PaymentInput {
method: PaymentMethod::Card {
number: "4111111111111112".into(), name: "Jane Doe".into(),
},
};
match validate_payment(bad_card) {
Validation::Success(_) => println!("Unexpected success!"),
Validation::Failure(errors) => {
println!("Validation failed (card number masked in error):");
for err in &errors {
println!(" - {}", err);
assert!(err.value.starts_with("****"));
}
}
}
println!("\n=== Invalid IBAN (checksum failure) ===");
let bad_iban = PaymentInput {
method: PaymentMethod::BankTransfer {
iban: "DE00370400440532013000".into(), bic: "COBADEFFXXX".into(),
},
};
match validate_payment(bad_iban) {
Validation::Success(_) => println!("Unexpected success!"),
Validation::Failure(errors) => {
println!("Validation failed (IBAN masked in error):");
for err in &errors {
println!(" - {}", err);
}
}
}
println!("\n=== Full Checkout Validation ===");
let checkout = CheckoutForm {
primary_card: "4111111111111111".into(),
backup_card: Some("5500000000000004".into()), bank_account: Some("GB82WEST12345698765432".into()), };
match validate_checkout(checkout) {
Validation::Success(valid) => {
println!("Checkout valid!");
println!("\n{}", generate_receipt(&valid));
}
Validation::Failure(errors) => {
println!("Checkout validation failed with {} errors:", errors.len());
for err in errors {
println!(" - {}", err);
}
}
}
println!("\n=== Checkout with Invalid Backup Card ===");
let bad_checkout = CheckoutForm {
primary_card: "4111111111111111".into(),
backup_card: Some("1234567890123456".into()), bank_account: Some("INVALID_IBAN".into()), };
match validate_checkout(bad_checkout) {
Validation::Success(_) => println!("Unexpected success!"),
Validation::Failure(errors) => {
println!("All {} errors collected:", errors.len());
for err in &errors {
println!(" - {}", err);
}
}
}
println!("\n=== Card Format Variations ===");
let formats = [
"4111111111111111", "4111 1111 1111 1111", "4111-1111-1111-1111", "5500000000000004", "340000000000009", "4111111111111112", ];
for card in formats {
match CreditCardNumber::new(card.to_string()) {
Ok(valid) => println!(" '{}' -> valid ({})", card, valid.masked()),
Err(e) => println!(" '{}' -> {}", card, e),
}
}
println!("\n=== IBAN Country Examples ===");
let ibans = [
("DE89370400440532013000", "Germany"),
("GB82WEST12345698765432", "UK"),
("FR7630006000011234567890189", "France"),
("ES9121000418450200051332", "Spain"),
("de89370400440532013000", "Germany (lowercase)"),
("INVALID", "Invalid"),
];
for (iban, country) in ibans {
match Iban::new(iban.to_string()) {
Ok(valid) => println!(
" {} ({}): {} -> {}",
country,
valid.country_code(),
iban,
valid.masked()
),
Err(e) => println!(" {}: {} -> {}", country, iban, e),
}
}
println!("\n=== Security Features ===");
println!("1. Credit card numbers are masked in error messages (****1234)");
println!("2. IBANs are partially masked (DE89****3000)");
println!("3. Extension traits provide safe display methods");
println!("4. Sensitive data never appears in full in logs");
}