use stilltypes::prelude::*;
use stillwater::validation::Validation;
#[derive(Debug)]
struct DiscountInput {
base_discount: f64,
member_bonus: f64,
probability: f64,
}
#[derive(Debug)]
struct ValidDiscount {
base_discount: Percentage,
member_bonus: Percentage,
probability: UnitInterval,
}
fn validate_discount(input: DiscountInput) -> Validation<ValidDiscount, Vec<DomainError>> {
let base_v: Validation<Percentage, Vec<DomainError>> =
Validation::from_result(Percentage::new(input.base_discount).map_err(|e| vec![e]));
let bonus_v: Validation<Percentage, Vec<DomainError>> =
Validation::from_result(Percentage::new(input.member_bonus).map_err(|e| vec![e]));
let prob_v: Validation<UnitInterval, Vec<DomainError>> =
Validation::from_result(UnitInterval::new(input.probability).map_err(|e| vec![e]));
use stillwater::validation::ValidateAll;
(base_v, bonus_v, prob_v)
.validate_all()
.map(|(base_discount, member_bonus, probability)| ValidDiscount {
base_discount,
member_bonus,
probability,
})
}
fn calculate_discounted_price(
price: f64,
base_discount: &Percentage,
member_bonus: &Percentage,
) -> f64 {
let base_off = base_discount.of(price);
let bonus_off = member_bonus.of(price);
let total_off = (base_off + bonus_off).min(price); price - total_off
}
fn weighted_score(base_score: f64, weight: &UnitInterval) -> f64 {
weight.scale(base_score)
}
fn main() {
println!("Stilltypes Discount Validation Example");
println!("======================================\n");
println!("=== Valid Discount Configuration ===");
let valid_input = DiscountInput {
base_discount: 20.0,
member_bonus: 10.0,
probability: 0.75,
};
match validate_discount(valid_input) {
Validation::Success(discount) => {
println!("Discount configuration valid!");
println!(" Base discount: {}%", discount.base_discount.get());
println!(" Member bonus: {}%", discount.member_bonus.get());
println!(
" Probability: {} ({}%)",
discount.probability.get(),
discount.probability.to_percentage().get()
);
let original_price = 100.0;
let final_price = calculate_discounted_price(
original_price,
&discount.base_discount,
&discount.member_bonus,
);
println!("\n Price calculation:");
println!(" Original: ${:.2}", original_price);
println!(
" Base discount ({}%): -${:.2}",
discount.base_discount.get(),
discount.base_discount.of(original_price)
);
println!(
" Member bonus ({}%): -${:.2}",
discount.member_bonus.get(),
discount.member_bonus.of(original_price)
);
println!(" Final price: ${:.2}", final_price);
let base_score = 100.0;
let weighted = weighted_score(base_score, &discount.probability);
println!("\n Weighted score:");
println!(" Base score: {}", base_score);
println!(" Weight: {}", discount.probability.get());
println!(" Result: {}", weighted);
}
Validation::Failure(errors) => {
println!("Validation failed!");
for err in errors {
println!(" - {}", err);
}
}
}
println!("\n=== Invalid Discount Configuration (all fields wrong) ===");
let invalid_input = DiscountInput {
base_discount: 150.0, member_bonus: -10.0, probability: 1.5, };
match validate_discount(invalid_input) {
Validation::Success(_) => println!("Unexpected success!"),
Validation::Failure(errors) => {
println!("Validation failed with {} errors:", errors.len());
for err in &errors {
println!(" - {}", err);
}
}
}
println!("\n=== Percentage Conversions ===");
println!("\nConverting decimals to percentages:");
for decimal in [0.0, 0.25, 0.5, 0.75, 1.0] {
match Percentage::from_decimal(decimal) {
Ok(pct) => println!(" {:.2} -> {}%", decimal, pct.get()),
Err(e) => println!(" {:.2} -> Error: {}", decimal, e),
}
}
println!("\nConverting percentages to decimals:");
for percent in [0.0, 25.0, 50.0, 75.0, 100.0] {
let pct = Percentage::new(percent).unwrap();
println!(" {}% -> {:.2}", pct.get(), pct.to_decimal());
}
println!("\n=== Complement Calculations ===");
let discount = Percentage::new(30.0).unwrap();
let remaining = discount.complement();
println!(
"If discount is {}%, remaining is {}%",
discount.get(),
remaining.get()
);
let probability = UnitInterval::new(0.3).unwrap();
let complement = probability.complement();
println!(
"If probability is {}, complement is {}",
probability.get(),
complement.get()
);
println!("\n=== UnitInterval for Opacity ===");
for alpha in [0.0, 0.25, 0.5, 0.75, 1.0] {
let opacity = UnitInterval::new(alpha).unwrap();
let byte_value = opacity.scale(255.0);
println!(
" Opacity {} -> {} byte value ({:.0}/255)",
alpha,
opacity.to_percentage().get(),
byte_value
);
}
println!("\n=== Edge Cases and Error Messages ===");
println!("\nInvalid percentages:");
for value in [-1.0, 101.0, f64::NAN, f64::INFINITY] {
match Percentage::new(value) {
Ok(_) => println!(" {} -> valid (unexpected)", value),
Err(e) => println!(" {} -> {}", value, e),
}
}
println!("\nInvalid unit intervals:");
for value in [-0.1, 1.1, f64::NAN, f64::NEG_INFINITY] {
match UnitInterval::new(value) {
Ok(_) => println!(" {} -> valid (unexpected)", value),
Err(e) => println!(" {} -> {}", value, e),
}
}
println!("\n=== Type Conversions ===");
let unit = UnitInterval::new(0.42).unwrap();
let pct = unit.to_percentage();
println!("UnitInterval {} -> Percentage {}%", unit.get(), pct.get());
let pct2 = Percentage::new(67.5).unwrap();
let unit2 = UnitInterval::from_percentage(*pct2.get()).unwrap();
println!("Percentage {}% -> UnitInterval {}", pct2.get(), unit2.get());
}