Loading page…
Rust walkthroughs
Loading page…
The predicates crate provides a functional, composable way to define predicates (boolean-valued functions) that can be used for testing and assertions. Instead of writing custom assertion logic, you chain predicates together to create expressive, readable tests. The crate integrates seamlessly with assertion libraries and provides clear failure messages. Predicates can test equality, ordering, string contents, path properties, and can be combined with boolean operators (and, or, not). This leads to more maintainable tests with better error messages when they fail.
Key concepts:
and(), or(), not()Predicate trait for domain-specific logicassert!(pred.eval(value)) pattern# Cargo.toml
[dependencies]
predicates = "3"use predicates::prelude::*;
fn main() {
let predicate = predicate::eq(5);
assert!(predicate.eval(&5));
assert!(!predicate.eval(&3));
println!("Is 5 equal to 5? {}", predicate.eval(&5));
}use predicates::prelude::*;
fn main() {
// Equality
let is_five = predicate::eq(5);
println!("5 == 5: {}", is_five.eval(&5));
println!("3 == 5: {}", is_five.eval(&3));
// Inequality
let not_five = predicate::ne(5);
println!("3 != 5: {}", not_five.eval(&3));
// Equality with floating point tolerance
let approx_pi = predicate::near(3.14159).epsilon(0.0001);
println!("pi is approximately 3.14159: {}", approx_pi.eval(&3.14160));
// Works with strings
let is_hello = predicate::eq("hello");
println!("'hello' == 'hello': {}", is_hello.eval("hello"));
}use predicates::prelude::*;
fn main() {
// Greater than
let is_positive = predicate::gt(0);
println!("5 > 0: {}", is_positive.eval(&5));
println!("-1 > 0: {}", is_positive.eval(&-1));
// Greater than or equal
let at_least_ten = predicate::ge(10);
println!("10 >= 10: {}", at_least_ten.eval(&10));
println!("5 >= 10: {}", at_least_ten.eval(&5));
// Less than
let is_child = predicate::lt(18);
println!("12 < 18: {}", is_child.eval(&12));
// Less than or equal
let at_most_hundred = predicate::le(100);
println!("100 <= 100: {}", at_most_hundred.eval(&100));
}use predicates::prelude::*;
fn main() {
// Contains substring
let contains_world = predicate::str::contains("world");
println!("'hello world' contains 'world': {}", contains_world.eval("hello world"));
println!("'hello there' contains 'world': {}", contains_world.eval("hello there"));
// Starts with
let starts_hello = predicate::str::starts_with("hello");
println!("'hello world' starts with 'hello': {}", starts_hello.eval("hello world"));
// Ends with
let ends_rust = predicate::str::ends_with(".rs");
println!("'main.rs' ends with '.rs': {}", ends_rust.eval("main.rs"));
// Matches regex
let is_email = predicate::str::is_match(r"^[a-z]+@[a-z]+\.[a-z]+$").unwrap();
println!("'user@example.com' matches email pattern: {}", is_email.eval("user@example.com"));
println!("'not-an-email' matches email pattern: {}", is_email.eval("not-an-email"));
// Is empty
let is_empty = predicate::str::is_empty();
println!("'' is empty: {}", is_empty.eval(""));
println!("'text' is empty: {}", is_empty.eval("text"));
// Is not empty
let is_not_empty = predicate::str::is_empty().not();
println!("'text' is not empty: {}", is_not_empty.eval("text"));
}use predicates::prelude::*;
fn main() {
// Case-insensitive contains
let contains_hello = predicate::str::contains("HELLO").trim().normalize();
println!("'hello world' contains 'HELLO' (normalized): {}",
contains_hello.eval("hello world"));
}use predicates::prelude::*;
use std::path::Path;
fn main() {
// Exists
let exists = predicate::path::exists();
println!("'/etc/hosts' exists: {}", exists.eval(Path::new("/etc/hosts")));
// Is a file
let is_file = predicate::path::is_file();
println!("'/etc/hosts' is file: {}", is_file.eval(Path::new("/etc/hosts")));
// Is a directory
let is_dir = predicate::path::is_dir();
println!("'/etc' is directory: {}", is_dir.eval(Path::new("/etc")));
// File extension
let is_rust = predicate::path::extension().eq("rs");
println!("'main.rs' has extension 'rs': {}", is_rust.eval(Path::new("main.rs")));
// File name
let is_config = predicate::path::file_name().eq("config.json");
println!("'/app/config.json' has filename 'config.json': {}",
is_config.eval(Path::new("/app/config.json")));
}use predicates::prelude::*;
fn main() {
// Both conditions must be true
let is_adult = predicate::ge(18);
let is_senior = predicate::lt(65);
let is_working_age = is_adult.and(is_senior);
println!("25 is working age: {}", is_working_age.eval(&25));
println!("10 is working age: {}", is_working_age.eval(&10));
println!("70 is working age: {}", is_working_age.eval(&70));
// Chain multiple conditions
let is_valid_score = predicate::ge(0).and(predicate::le(100));
println!("50 is valid score: {}", is_valid_score.eval(&50));
println!("150 is valid score: {}", is_valid_score.eval(&150));
}use predicates::prelude::*;
fn main() {
// Either condition can be true
let is_weekend = predicate::eq("Saturday").or(predicate::eq("Sunday"));
println!("Saturday is weekend: {}", is_weekend.eval("Saturday"));
println!("Sunday is weekend: {}", is_weekend.eval("Sunday"));
println!("Monday is weekend: {}", is_weekend.eval("Monday"));
// Complex OR chain
let is_holiday = predicate::eq("Christmas")
.or(predicate::eq("Thanksgiving"))
.or(predicate::eq("New Year"));
println!("Christmas is holiday: {}", is_holiday.eval("Christmas"));
println!("Tuesday is holiday: {}", is_holiday.eval("Tuesday"));
}use predicates::prelude::*;
fn main() {
// Negate a predicate
let is_empty = predicate::str::is_empty();
let is_not_empty = is_empty.not();
println!("'' is not empty: {}", is_not_empty.eval(""));
println!("'text' is not empty: {}", is_not_empty.eval("text"));
// Negate complex predicates
let is_admin = predicate::eq("admin");
let is_not_admin = is_admin.not();
println!("'user' is not admin: {}", is_not_admin.eval("user"));
println!("'admin' is not admin: {}", is_not_admin.eval("admin"));
}use predicates::prelude::*;
fn main() {
// Valid age: 0-17 (minor) OR 18-64 (adult) OR 65+ (senior)
// Let's say we want to check if someone is NOT a minor
let is_minor = predicate::lt(18);
let is_not_minor = is_minor.not();
println!("20 is not minor: {}", is_not_minor.eval(&20));
println!("10 is not minor: {}", is_not_minor.eval(&10));
// Valid password: at least 8 chars AND contains number AND contains letter
let has_min_length = predicate::str::len().ge(8);
let has_digit = predicate::str::contains("1")
.or(predicate::str::contains("2"))
.or(predicate::str::contains("3"))
.or(predicate::str::contains("4"))
.or(predicate::str::contains("5"))
.or(predicate::str::contains("6"))
.or(predicate::str::contains("7"))
.or(predicate::str::contains("8"))
.or(predicate::str::contains("9"))
.or(predicate::str::contains("0"));
// Simplified: just check length for demo
let valid_password = has_min_length;
println!("'password123' is valid: {}", valid_password.eval("password123"));
println!("'pass' is valid: {}", valid_password.eval("pass"));
}use predicates::prelude::*;
fn main() {
// Approximate equality with epsilon
let is_close = predicate::near(3.14159).epsilon(0.01);
println!("3.14 is close to 3.14159 (epsilon=0.01): {}", is_close.eval(&3.14));
println!("3.0 is close to 3.14159 (epsilon=0.01): {}", is_close.eval(&3.0));
// Different epsilon values
let is_approx_pi = predicate::near(3.14159).epsilon(0.001);
println!("3.142 is approximately pi: {}", is_approx_pi.eval(&3.142));
}use predicates::prelude::*;
fn main() {
// Check if any element matches
let has_positive = predicate::iter::any(predicate::gt(0));
println!("[-1, 0, 1] has positive: {}", has_positive.eval(&[-1, 0, 1]));
println!("[-1, -2, -3] has positive: {}", has_positive.eval(&[-1, -2, -3]));
// Check if all elements match
let all_positive = predicate::iter::all(predicate::gt(0));
println!("[1, 2, 3] all positive: {}", all_positive.eval(&[1, 2, 3]));
println!("[1, 0, 3] all positive: {}", all_positive.eval(&[1, 0, 3]));
// Check if iterator contains a specific value
let contains_five = predicate::iter::contains(5);
println!("[1, 2, 5, 3] contains 5: {}", contains_five.eval(&[1, 2, 5, 3]));
}use predicates::prelude::*;
fn main() {
// Always true/false
let always_true = predicate::always();
let always_false = predicate::never();
println!("always_true: {}", always_true.eval(&42));
println!("always_false: {}", always_false.eval(&42));
// Boolean function predicate
let is_even = predicate::function(|x: &i32| x % 2 == 0);
println!("4 is even: {}", is_even.eval(&4));
println!("3 is even: {}", is_even.eval(&3));
}use predicates::prelude::*;
fn main() {
// Custom function as predicate
let is_long_enough = predicate::function(|s: &&str| s.len() >= 5);
println!("'hello' is long enough: {}", is_long_enough.eval("hello"));
println!("'hi' is long enough: {}", is_long_enough.eval("hi"));
// Complex logic
let is_palindrome = predicate::function(|s: &&str| {
let chars: Vec<char> = s.chars().collect();
chars == chars.iter().rev().cloned().collect::<Vec<_>>()
});
println!("'racecar' is palindrome: {}", is_palindrome.eval("racecar"));
println!("'hello' is palindrome: {}", is_palindrome.eval("hello"));
}use predicates::prelude::*;
use predicates::BoxPredicate;
fn main() {
// Create a boxed predicate for runtime selection
fn get_validator(min: i32, max: i32) -> BoxPredicate<i32> {
predicate::ge(min).and(predicate::le(max)).boxed()
}
let validator = get_validator(10, 100);
println!("50 is in range: {}", validator.eval(&50));
println!("5 is in range: {}", validator.eval(&5));
println!("150 is in range: {}", validator.eval(&150));
}use predicates::prelude::*;
struct User {
username: String,
email: String,
age: u32,
role: String,
}
fn validate_user(user: &User) -> Result<(), String> {
let valid_username = predicate::str::len().ge(3)
.and(predicate::str::len().le(20));
let valid_email = predicate::str::contains("@");
let valid_age = predicate::ge(13).and(predicate::le(120));
let valid_role = predicate::eq("admin")
.or(predicate::eq("user"))
.or(predicate::eq("guest"));
if !valid_username.eval(&user.username.as_str()) {
return Err("Username must be 3-20 characters".to_string());
}
if !valid_email.eval(&user.email.as_str()) {
return Err("Invalid email format".to_string());
}
if !valid_age.eval(&user.age) {
return Err("Age must be 13-120".to_string());
}
if !valid_role.eval(&user.role.as_str()) {
return Err("Invalid role".to_string());
}
Ok(())
}
fn main() {
let good_user = User {
username: "alice".to_string(),
email: "alice@example.com".to_string(),
age: 25,
role: "user".to_string(),
};
let bad_user = User {
username: "ab".to_string(), // too short
email: "invalid-email".to_string(),
age: 10, // too young
role: "superadmin".to_string(), // invalid role
};
match validate_user(&good_user) {
Ok(()) => println!("Good user is valid!"),
Err(e) => println!("Good user error: {}", e),
}
match validate_user(&bad_user) {
Ok(()) => println!("Bad user is valid!"),
Err(e) => println!("Bad user error: {}", e),
}
}use predicates::prelude::*;
use std::collections::HashMap;
struct Config {
settings: HashMap<String, String>,
}
impl Config {
fn new() -> Self {
Self { settings: HashMap::new() }
}
fn set(&mut self, key: &str, value: &str) {
self.settings.insert(key.to_string(), value.to_string());
}
fn get(&self, key: &str) -> Option<&String> {
self.settings.get(key)
}
}
fn validate_config(config: &Config) -> Vec<String> {
let mut errors = Vec::new();
// Required keys
let required_keys = ["host", "port", "database"];
for key in required_keys {
let key_exists = predicate::path::exists();
if config.get(key).is_none() {
errors.push(format!("Missing required key: {}", key));
}
}
// Validate port is numeric
if let Some(port) = config.get("port") {
let is_numeric = predicate::str::is_match(r"^\d+$").unwrap();
if !is_numeric.eval(port) {
errors.push("Port must be numeric".to_string());
}
let valid_port = predicate::function(|p: &&str| {
p.parse::<u16>().is_ok()
});
if !valid_port.eval(port) {
errors.push("Port must be 1-65535".to_string());
}
}
// Validate host is not empty
if let Some(host) = config.get("host") {
let is_not_empty = predicate::str::is_empty().not();
if !is_not_empty.eval(host) {
errors.push("Host cannot be empty".to_string());
}
}
errors
}
fn main() {
let mut config = Config::new();
config.set("host", "localhost");
config.set("port", "8080");
config.set("database", "mydb");
let errors = validate_config(&config);
if errors.is_empty() {
println!("Config is valid!");
} else {
println!("Config errors: {:?}", errors);
}
let mut bad_config = Config::new();
bad_config.set("host", "");
bad_config.set("port", "abc");
let errors = validate_config(&bad_config);
println!("Bad config errors: {:?}", errors);
}use predicates::prelude::*;
struct ApiResponse {
status: u16,
body: String,
headers: Vec<(String, String)>,
}
fn validate_response(response: &ApiResponse) -> Result<(), String> {
// Status should be 2xx
let is_success_status = predicate::ge(200).and(predicate::lt(300));
if !is_success_status.eval(&response.status) {
return Err(format!("Unexpected status: {}", response.status));
}
// Body should be valid JSON (simplified check)
let is_json = predicate::str::starts_with("{")
.or(predicate::str::starts_with("["));
if !is_json.eval(&response.body.as_str()) {
return Err("Response body is not JSON".to_string());
}
// Should have content-type header
let has_content_type = predicate::function(|headers: &Vec<(String, String)>| {
headers.iter().any(|(k, _)| k.to_lowercase() == "content-type")
});
if !has_content_type.eval(&response.headers) {
return Err("Missing Content-Type header".to_string());
}
Ok(())
}
fn main() {
let good_response = ApiResponse {
status: 200,
body: r#"{"message": "ok"}"#.to_string(),
headers: vec![
("Content-Type".to_string(), "application/json".to_string()),
],
};
let bad_response = ApiResponse {
status: 500,
body: "Internal Server Error".to_string(),
headers: vec![],
};
match validate_response(&good_response) {
Ok(()) => println!("Good response is valid!"),
Err(e) => println!("Good response error: {}", e),
}
match validate_response(&bad_response) {
Ok(()) => println!("Bad response is valid!"),
Err(e) => println!("Bad response error: {}", e),
}
}use predicates::prelude::*;
use std::path::Path;
fn create_file_filter() -> impl Predicate<Path> {
// Rust or Markdown files
let is_rust = predicate::path::extension().eq("rs");
let is_markdown = predicate::path::extension().eq("md");
let is_code = is_rust.or(is_markdown);
// Not in target directory
let not_in_target = predicate::path::ends_with("target").not();
is_code.and(not_in_target)
}
fn main() {
let filter = create_file_filter();
let files = vec![
Path::new("src/main.rs"),
Path::new("README.md"),
Path::new("target/main.rs"),
Path::new("Cargo.toml"),
Path::new("docs/guide.md"),
];
println!("Files matching filter:");
for file in files {
if filter.eval(file) {
println!(" {}", file.display());
}
}
}use predicates::prelude::*;
struct Record {
id: i32,
name: String,
score: f64,
tags: Vec<String>,
}
fn check_data_quality(records: &[Record]) -> Vec<String> {
let mut issues = Vec::new();
let valid_id = predicate::gt(0);
let valid_name = predicate::str::len().ge(1).and(predicate::str::len().le(100));
let valid_score = predicate::ge(0.0).and(predicate::le(100.0));
let has_tags = predicate::function(|tags: &Vec<String>| !tags.is_empty());
for (i, record) in records.iter().enumerate() {
if !valid_id.eval(&record.id) {
issues.push(format!("Record {}: Invalid ID {}", i, record.id));
}
if !valid_name.eval(&record.name.as_str()) {
issues.push(format!("Record {}: Invalid name '{}'", i, record.name));
}
if !valid_score.eval(&record.score) {
issues.push(format!("Record {}: Invalid score {}", i, record.score));
}
if !has_tags.eval(&record.tags) {
issues.push(format!("Record {}: No tags", i));
}
}
issues
}
fn main() {
let records = vec![
Record {
id: 1,
name: "Alice".to_string(),
score: 95.5,
tags: vec!["vip".to_string()],
},
Record {
id: -1, // Invalid
name: "".to_string(), // Invalid
score: 150.0, // Invalid
tags: vec![], // Invalid
},
Record {
id: 2,
name: "Bob".to_string(),
score: 75.0,
tags: vec!["new".to_string()],
},
];
let issues = check_data_quality(&records);
if issues.is_empty() {
println!("All records pass quality checks!");
} else {
println!("Data quality issues:");
for issue in issues {
println!(" - {}", issue);
}
}
}use predicates::prelude::*;
// Simulated test functions
fn assert_true(condition: bool, message: &str) {
if condition {
println!("✓ {}", message);
} else {
println!("✗ {}", message);
}
}
#[test]
fn test_user_operations() {
let result = 42;
// Using predicates for assertions
assert_true(
predicate::eq(42).eval(&result),
"Result should be 42"
);
assert_true(
predicate::gt(40).and(predicate::lt(50)).eval(&result),
"Result should be between 40 and 50"
);
}
#[test]
fn test_string_operations() {
let message = "Hello, World!";
assert_true(
predicate::str::contains("World").eval(message),
"Message should contain 'World'"
);
assert_true(
predicate::str::starts_with("Hello").eval(message),
"Message should start with 'Hello'"
);
assert_true(
predicate::str::len().eq(13).eval(message),
"Message should have length 13"
);
}
fn main() {
test_user_operations();
test_string_operations();
}use predicates::prelude::*;
fn main() {
let pred = predicate::gt(5).and(predicate::lt(10));
// The predicate can be displayed
println!("Predicate: {}", pred);
// Debug output shows the structure
println!("Debug: {:?}", pred);
// Complex predicate
let complex = predicate::str::contains("hello")
.or(predicate::str::contains("hi"))
.and(predicate::str::len().ge(3));
println!("Complex predicate: {}", complex);
}predicate::eq(value) creates an equality predicatepredicate::gt/ge/lt/le(value) create ordering predicatespredicate::str::contains/starts_with/ends_with(text) for string matchingpredicate::str::is_match(regex) for regex matchingpredicate::path::exists/is_file/is_dir() for path checks.and(other) combines predicates with AND.or(other) combines predicates with OR.not() negates a predicatepredicate::iter::any/all(pred) for collection checkingpredicate::function(|x| ...) for custom logic.boxed() creates a BoxPredicate for dynamic dispatchpred.eval(&value) evaluates the predicate