use stillwater::predicate::*;
use stillwater::Validation;
fn main() {
println!("=== Predicate Combinators Example ===\n");
string_predicates();
number_predicates();
collection_predicates();
logical_combinators();
validation_integration();
real_world_example();
}
fn string_predicates() {
println!("--- String Predicates ---\n");
let p = len_between(3, 10);
println!("len_between(3, 10):");
println!(" 'ab': {}", p.check("ab")); println!(" 'abc': {}", p.check("abc")); println!(" 'hello': {}", p.check("hello")); println!(" '12345678901': {}", p.check("12345678901"));
println!("\nlen_min(3).check('hi'): {}", len_min(3).check("hi")); println!("len_max(5).check('hello'): {}", len_max(5).check("hello")); println!("len_eq(5).check('hello'): {}", len_eq(5).check("hello"));
println!(
"\nstarts_with('http').check('https://example.com'): {}",
starts_with("http").check("https://example.com")
); println!(
"ends_with('.rs').check('main.rs'): {}",
ends_with(".rs").check("main.rs")
); println!(
"contains('@').check('user@example.com'): {}",
contains("@").check("user@example.com")
);
println!(
"\nis_alphabetic().check('hello'): {}",
is_alphabetic().check("hello")
); println!(
"is_alphanumeric().check('hello123'): {}",
is_alphanumeric().check("hello123")
); println!(
"is_numeric().check('12345'): {}",
is_numeric().check("12345")
); println!("is_ascii().check('hello'): {}", is_ascii().check("hello")); println!("is_ascii().check('héllo'): {}", is_ascii().check("héllo"));
let valid_identifier = all_chars(|c: char| c.is_alphanumeric() || c == '_');
println!("\nall_chars(alphanumeric or '_'):");
println!(" 'user_name': {}", valid_identifier.check("user_name")); println!(" 'user-name': {}", valid_identifier.check("user-name"));
let has_digit = any_char(char::is_numeric);
println!("\nany_char(numeric):");
println!(" 'hello123': {}", has_digit.check("hello123")); println!(" 'hello': {}", has_digit.check("hello"));
println!();
}
fn number_predicates() {
println!("--- Number Predicates ---\n");
println!("gt(5).check(&6): {}", gt(5).check(&6)); println!("gt(5).check(&5): {}", gt(5).check(&5)); println!("ge(5).check(&5): {}", ge(5).check(&5)); println!("lt(5).check(&4): {}", lt(5).check(&4)); println!("le(5).check(&5): {}", le(5).check(&5)); println!("eq(5).check(&5): {}", eq(5).check(&5)); println!("ne(5).check(&4): {}", ne(5).check(&4));
let valid_age = between(0, 150);
println!("\nbetween(0, 150):");
println!(" 25: {}", valid_age.check(&25)); println!(" -5: {}", valid_age.check(&-5)); println!(" 200: {}", valid_age.check(&200));
println!(
"\npositive::<i32>().check(&5): {}",
positive::<i32>().check(&5)
); println!(
"positive::<i32>().check(&0): {}",
positive::<i32>().check(&0)
); println!(
"negative::<i32>().check(&-5): {}",
negative::<i32>().check(&-5)
); println!(
"non_negative::<i32>().check(&0): {}",
non_negative::<i32>().check(&0)
);
let valid_probability = between(0.0_f64, 1.0_f64);
println!("\nbetween(0.0, 1.0) for f64:");
println!(" 0.5: {}", valid_probability.check(&0.5)); println!(" 1.5: {}", valid_probability.check(&1.5));
println!();
}
fn collection_predicates() {
println!("--- Collection Predicates ---\n");
let numbers = vec![1, 2, 3, 4, 5];
let empty: Vec<i32> = vec![];
println!("is_empty().check(&[1,2,3]): {}", is_empty().check(&numbers)); println!("is_empty().check(&[]): {}", is_empty().check(&empty)); println!(
"is_not_empty().check(&[1,2,3]): {}",
is_not_empty().check(&numbers)
);
println!(
"\nhas_len(5).check(&[1,2,3,4,5]): {}",
has_len(5).check(&numbers)
); println!(
"has_min_len(3).check(&[1,2,3,4,5]): {}",
has_min_len(3).check(&numbers)
); println!(
"has_max_len(10).check(&[1,2,3,4,5]): {}",
has_max_len(10).check(&numbers)
);
let all_positive = all(positive::<i32>());
println!("\nall(positive):");
println!(" [1,2,3]: {}", all_positive.check(&vec![1, 2, 3])); println!(" [1,-2,3]: {}", all_positive.check(&vec![1, -2, 3]));
let any_gt_10 = any(gt(10));
println!("\nany(gt(10)):");
println!(" [1,2,15]: {}", any_gt_10.check(&vec![1, 2, 15])); println!(" [1,2,3]: {}", any_gt_10.check(&vec![1, 2, 3]));
println!(
"\ncontains_element(3).check(&[1,2,3]): {}",
contains_element(3).check(&vec![1, 2, 3])
); println!(
"contains_element(5).check(&[1,2,3]): {}",
contains_element(5).check(&vec![1, 2, 3])
);
println!();
}
fn logical_combinators() {
println!("--- Logical Combinators ---\n");
let in_range = gt(0).and(lt(100));
println!("gt(0).and(lt(100)):");
println!(" 50: {}", in_range.check(&50)); println!(" 0: {}", in_range.check(&0)); println!(" 100: {}", in_range.check(&100));
let extreme = lt(0).or(gt(100));
println!("\nlt(0).or(gt(100)):");
println!(" -5: {}", extreme.check(&-5)); println!(" 150: {}", extreme.check(&150)); println!(" 50: {}", extreme.check(&50));
let not_positive = positive::<i32>().not();
println!("\npositive::<i32>().not():");
println!(" -5: {}", not_positive.check(&-5)); println!(" 0: {}", not_positive.check(&0)); println!(" 5: {}", not_positive.check(&5));
let complex = gt(0).and(lt(100)).or(eq(0));
println!("\ngt(0).and(lt(100)).or(eq(0)):");
println!(" 0: {}", complex.check(&0)); println!(" 50: {}", complex.check(&50)); println!(" -5: {}", complex.check(&-5));
let all_greater = all_of([gt(0), gt(-10), gt(-100)]);
println!("\nall_of([gt(0), gt(-10), gt(-100)]):");
println!(" 50: {}", all_greater.check(&50)); println!(" -50: {}", all_greater.check(&-50));
let special_values = any_of([eq(1), eq(5), eq(10)]);
println!("\nany_of([eq(1), eq(5), eq(10)]):");
println!(" 5: {}", special_values.check(&5)); println!(" 7: {}", special_values.check(&7));
let no_special = none_of([eq(1), eq(5), eq(10)]);
println!("\nnone_of([eq(1), eq(5), eq(10)]):");
println!(" 7: {}", no_special.check(&7)); println!(" 5: {}", no_special.check(&5));
println!();
}
fn validation_integration() {
println!("--- Validation Integration ---\n");
let result = validate(String::from("hello"), len_min(3), "too short");
println!("validate('hello', len_min(3), 'too short'): {:?}", result);
let result = validate(String::from("hi"), len_min(3), "too short");
println!("validate('hi', len_min(3), 'too short'): {:?}", result);
let result = validate_with(String::from("hi"), len_min(3), |s| {
format!("'{}' is too short (min 3 chars)", s)
});
println!(
"\nvalidate_with('hi', len_min(3), <error_fn>): {:?}",
result
);
let result = Validation::success(String::from("hello"))
.ensure(len_min(3), "too short")
.ensure(len_max(10), "too long")
.ensure(is_alphabetic(), "must be alphabetic");
println!("\nValidation chain with ensure():");
println!(" 'hello': {:?}", result);
let result = Validation::success(String::from("hello123"))
.ensure(len_min(3), "too short")
.ensure(len_max(10), "too long")
.ensure(is_alphabetic(), "must be alphabetic");
println!(" 'hello123': {:?}", result);
let result = validate(25, between(0, 150), "age out of range");
println!(
"\nvalidate(25, between(0, 150), 'age out of range'): {:?}",
result
);
let result = validate(-5, between(0, 150), "age out of range");
println!(
"validate(-5, between(0, 150), 'age out of range'): {:?}",
result
);
let result: Validation<Vec<i32>, &str> = validate(
vec![1, 2, 3],
all(positive::<i32>()),
"all values must be positive",
);
println!("\nvalidate([1,2,3], all(positive()), ...): {:?}", result);
let result: Validation<Vec<i32>, &str> = validate(
vec![1, -2, 3],
all(positive::<i32>()),
"all values must be positive",
);
println!("validate([1,-2,3], all(positive()), ...): {:?}", result);
println!();
}
fn real_world_example() {
println!("--- Real World Example: User Registration ---\n");
#[derive(Debug, Clone)]
struct UserInput {
username: String,
email: String,
age: i32,
tags: Vec<String>,
}
#[derive(Debug)]
struct User {
username: String,
email: String,
age: i32,
tags: Vec<String>,
}
impl User {
fn display(&self) -> String {
format!(
"User {{ username: {}, email: {}, age: {}, tags: {:?} }}",
self.username, self.email, self.age, self.tags
)
}
}
fn validate_username(username: &str) -> Validation<(), Vec<String>> {
let mut errors = Vec::new();
if !len_between(3, 20).check(username) {
errors.push("Username must be 3-20 characters".to_string());
}
if !all_chars(|c: char| c.is_alphanumeric() || c == '_').check(username) {
errors.push("Username must contain only letters, numbers, and underscores".to_string());
}
if errors.is_empty() {
Validation::success(())
} else {
Validation::failure(errors)
}
}
fn validate_email(email: &str) -> Validation<(), Vec<String>> {
let mut errors = Vec::new();
if !not_empty().check(email) {
errors.push("Email is required".to_string());
} else if !contains("@").check(email) {
errors.push("Email must contain @".to_string());
}
if errors.is_empty() {
Validation::success(())
} else {
Validation::failure(errors)
}
}
fn validate_age(age: i32) -> Validation<(), Vec<String>> {
if between(0, 150).check(&age) {
Validation::success(())
} else {
Validation::failure(vec![format!("Age {} is out of valid range (0-150)", age)])
}
}
fn validate_tags(tags: &[String]) -> Validation<(), Vec<String>> {
if has_max_len(5).check(tags) {
Validation::success(())
} else {
Validation::failure(vec!["Maximum 5 tags allowed".to_string()])
}
}
fn validate_user(input: UserInput) -> Validation<User, Vec<String>> {
let v1 = validate_username(&input.username);
let v2 = validate_email(&input.email);
let v3 = validate_age(input.age);
let v4 = validate_tags(&input.tags);
Validation::<((), (), (), ()), Vec<String>>::all((v1, v2, v3, v4)).map(|_| User {
username: input.username,
email: input.email,
age: input.age,
tags: input.tags,
})
}
let valid_input = UserInput {
username: "john_doe".to_string(),
email: "john@example.com".to_string(),
age: 25,
tags: vec!["rust".to_string(), "programming".to_string()],
};
println!("Valid input:");
println!(" {:?}", valid_input);
match validate_user(valid_input) {
Validation::Success(user) => println!(" Result: Success({})", user.display()),
Validation::Failure(errors) => println!(" Result: Failure({:?})", errors),
}
let invalid_input = UserInput {
username: "ab".to_string(), email: "invalid".to_string(), age: 200, tags: vec!["a".to_string(); 10], };
println!("\nInvalid input:");
println!(" {:?}", invalid_input);
match validate_user(invalid_input) {
Validation::Success(user) => println!(" Result: Success({})", user.display()),
Validation::Failure(errors) => {
println!(" Result: Failure with {} errors:", errors.len());
for error in errors {
println!(" - {}", error);
}
}
}
}