Loading page…
Rust walkthroughs
Loading page…
The predicates crate provides a fluent, composable API for creating predicate functions in Rust. It's especially powerful when combined with testing frameworks, enabling readable assertions like assert!(predicate::eq(5).eval(&value)). The library offers predicates for equality, ordering, string matching, collections, paths, and more. You can combine predicates with AND, OR, and NOT operations, and use Predicate trait for custom predicates. It integrates seamlessly with assert_cmd for testing command-line applications.
Key concepts:
and(), or(), not()eq, gt, lt, contains, matches and moreassert! macro# Cargo.toml
[dependencies]
predicates = "3"use predicates::prelude::*;
fn main() {
let value = 5;
let predicate = predicates::eq(5);
assert!(predicate.eval(&value));
println!("Value equals 5!");
}use predicates::prelude::*;
fn main() {
// Equality
let is_five = predicates::eq(5);
println!("5 is five: {}", is_five.eval(&5));
println!("3 is five: {}", is_five.eval(&3));
// Inequality
let is_not_five = predicates::ne(5);
println!("3 is not five: {}", is_not_five.eval(&3));
// Display the predicate
println!("\nPredicate description: {}", is_five);
}use predicates::prelude::*;
fn main() {
let value = 10;
// Greater than
let gt_five = predicates::gt(5);
println!("10 > 5: {}", gt_five.eval(&value));
// Greater than or equal
let gte_ten = predicates::ge(10);
println!("10 >= 10: {}", gte_ten.eval(&value));
// Less than
let lt_twenty = predicates::lt(20);
println!("10 < 20: {}", lt_twenty.eval(&value));
// Less than or equal
let lte_ten = predicates::le(10);
println!("10 <= 10: {}", lte_ten.eval(&value));
// In range (exclusive)
let in_range = predicates::ord::in_range(5..15);
println!("10 in 5..15: {}", in_range.eval(&value));
// In range (inclusive)
let in_inclusive = predicates::ord::in_range(5..=15);
println!("10 in 5..=15: {}", in_inclusive.eval(&value));
}use predicates::prelude::*;
fn main() {
let text = "Hello, World!";
// Contains substring
let contains_world = predicates::str::contains("World");
println!("Contains 'World': {}", contains_world.eval(text));
// Starts with
let starts_hello = predicates::str::starts_with("Hello");
println!("Starts with 'Hello': {}", starts_hello.eval(text));
// Ends with
let ends_exclaim = predicates::str::ends_with("!");
println!("Ends with '!': {}", ends_exclaim.eval(text));
// Is empty
let is_empty = predicates::str::is_empty();
println!("Is empty: {}", is_empty.eval(text));
println!("Is empty (empty string): {}", is_empty.eval(""));
// Regex match
let matches_pattern = predicates::str::is_match("Hello, \\w+!").unwrap();
println!("Matches pattern: {}", matches_pattern.eval(text));
// Case-insensitive comparison
let eq_ignore_case = predicates::str::similar("hello, world!");
println!("Case-insensitive match: {}", eq_ignore_case.eval(text));
}use predicates::prelude::*;
fn main() {
// Email-like pattern
let email_pattern = predicates::str::is_match(r"^[\w.]+@[\w.]+\.\w+$").unwrap();
println!("Valid email: {}", email_pattern.eval("user@example.com"));
println!("Invalid email: {}", email_pattern.eval("not-an-email"));
// Phone number pattern
let phone_pattern = predicates::str::is_match(r"^\d{3}-\d{3}-\d{4}$").unwrap();
println!("Valid phone: {}", phone_pattern.eval("555-123-4567"));
println!("Invalid phone: {}", phone_pattern.eval("5551234567"));
// IP address pattern
let ip_pattern = predicates::str::is_match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$").unwrap();
println!("Valid IP: {}", ip_pattern.eval("192.168.1.1"));
}use predicates::prelude::*;
fn main() {
let value = 10;
// AND: both must be true
let between_5_and_15 = predicates::gt(5).and(predicates::lt(15));
println!("10 between 5 and 15: {}", between_5_and_15.eval(&value));
println!("20 between 5 and 15: {}", between_5_and_15.eval(&20));
// OR: at least one must be true
let less_than_5_or_greater_than_15 = predicates::lt(5).or(predicates::gt(15));
println!("10 < 5 or > 15: {}", less_than_5_or_greater_than_15.eval(&value));
println!("20 < 5 or > 15: {}", less_than_5_or_greater_than_15.eval(&20));
// NOT: negate a predicate
let not_five = predicates::eq(5).not();
println!("10 is not 5: {}", not_five.eval(&value));
println!("5 is not 5: {}", not_five.eval(&5));
// Complex combinations
let complex = predicates::ge(0)
.and(predicates::le(100))
.and(predicates::ne(50).not()); // Is 50
println!("Is 50 (complex): {}", complex.eval(&50));
}use predicates::prelude::*;
fn main() {
let text = "Error: Connection failed";
// Check if error message
let is_error = predicates::str::contains("Error:")
.or(predicates::str::contains("error:"));
println!("Is error: {}", is_error.eval(text));
// Check if contains both 'Error' and 'failed'
let error_and_failed = predicates::str::contains("Error")
.and(predicates::str::contains("failed"));
println!("Error and failed: {}", error_and_failed.eval(text));
// Complex string matching
let valid_message = predicates::str::starts_with("Error:")
.and(predicates::str::contains("failed").not())
.or(predicates::str::starts_with("Success:"));
println!("Valid (not failed): {}", valid_message.eval("Error: timeout"));
println!("Valid (success): {}", valid_message.eval("Success: done"));
}use predicates::prelude::*;
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Contains element
let contains_three = predicates::iter::contains(&3);
println!("Contains 3: {}", contains_three.eval(&numbers.iter()));
// Check if any element satisfies predicate
let has_gt_three = predicates::iter::any(predicates::gt(3));
println!("Has element > 3: {}", has_gt_three.eval(&numbers.iter()));
// Check if all elements satisfy predicate
let all_positive = predicates::iter::all(predicates::gt(0));
println!("All positive: {}", all_positive.eval(&numbers.iter()));
// Count elements
let has_three_elements = predicates::iter::count(3);
let small_vec = vec![1, 2, 3];
println!("Has 3 elements: {}", has_three_elements.eval(&small_vec.iter()));
// Is empty
let is_empty = predicates::iter::empty();
println!("Is empty: {}", is_empty.eval(&Vec::<i32>::new().iter()));
}use predicates::prelude::*;
use std::path::Path;
fn main() {
// Create test paths
let existing_file = Path::new("/etc/hosts");
let non_existent = Path::new("/nonexistent/path");
// Exists
let exists = predicates::path::exists();
println!("/etc/hosts exists: {}", exists.eval(existing_file));
println!("nonexistent exists: {}", exists.eval(non_existent));
// Is a file
let is_file = predicates::path::is_file();
println!("Is file: {}", is_file.eval(existing_file));
// Is a directory
let is_dir = predicates::path::is_dir();
println!("/etc is dir: {}", is_dir.eval(Path::new("/etc")));
// File extension
let has_txt = predicates::path::has_extension("txt");
println!("Has .txt: {}", has_txt.eval(Path::new("file.txt")));
// File name
let matches_name = predicates::path::eq(Path::new("hosts"));
println!("Matches name: {}", matches_name.eval(existing_file));
}use predicates::prelude::*;
fn main() {
// Approximate equality for floats
let approx_pi = predicates::float::close(3.14159, 0.0001);
println!("3.14159 is close to π: {}", approx_pi.eval(&3.14159));
println!("3.14 is close to π: {}", approx_pi.eval(&3.14));
// Greater than or close
let gt_or_close = predicates::gt(10.0).or(predicates::float::close(10.0, 0.01));
println!("10.005 is valid: {}", gt_or_close.eval(&10.005));
// Using `close_to` with tolerance
let close_to_100 = predicates::float::close(100.0, 1.0);
println!("99.5 close to 100: {}", close_to_100.eval(&99.5));
println!("101.5 close to 100: {}", close_to_100.eval(&101.5));
}use predicates::prelude::*;
fn main() {
let is_true = predicates::boolean::is_true();
let is_false = predicates::boolean::is_false();
println!("true is true: {}", is_true.eval(&true));
println!("false is false: {}", is_false.eval(&false));
// Use with Option
let some_value: Option<i32> = Some(5);
let none_value: Option<i32> = None;
let is_some = predicates::option::some(predicates::gt(3));
println!("Some(5) > 3: {}", is_some.eval(&some_value));
println!("None > 3: {}", is_some.eval(&none_value));
let is_none = predicates::option::none();
println!("None is none: {}", is_none.eval(&none_value));
}use predicates::prelude::*;
fn main() {
// Option predicates
let some_gt_five = predicates::option::some(predicates::gt(5));
println!("Some(10) > 5: {}", some_gt_five.eval(&Some(10)));
println!("Some(3) > 5: {}", some_gt_five.eval(&Some(3)));
println!("None > 5: {}", some_gt_five.eval::<i32>(&None));
let is_none = predicates::option::none();
println!("None is none: {}", is_none.eval::<i32>(&None));
println!("Some(1) is none: {}", is_none.eval(&Some(1)));
// Result predicates
let ok_gt_five = predicates::result::ok(predicates::gt(5));
println!("Ok(10) > 5: {}", ok_gt_five.eval(&Ok::<i32, &str>(10)));
let is_err = predicates::result::err(predicates::str::contains("error"));
println!("Err has 'error': {}", is_err.eval(&Err::<i32, &str>("an error occurred")));
}use predicates::prelude::*;
use std::fmt;
// Implement the Predicate trait for custom logic
struct IsEven;
impl Predicate<i32> for IsEven {
fn eval(&self, variable: &i32) -> bool {
variable % 2 == 0
}
fn find_case(&self, expected: bool, variable: &i32) -> Option<Case> {
let result = self.eval(variable);
if result == expected {
Some(Case::new(Some(expected), variable))
} else {
None
}
}
}
impl predicates::reflection::PredicateReflection for IsEven {}
impl fmt::Display for IsEven {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "var.is_even()")
}
}
fn main() {
let is_even = IsEven;
println!("4 is even: {}", is_even.eval(&4));
println!("3 is even: {}", is_even.eval(&3));
// Use with standard assertions
assert!(is_even.eval(&6));
// Combine with other predicates
let even_and_gt_5 = IsEven.and(predicates::gt(5));
println!("6 is even and > 5: {}", even_and_gt_5.eval(&6));
}// In a real project, this would be in tests/
use predicates::prelude::*;
fn calculate(x: i32, y: i32) -> i32 {
x + y
}
fn process_name(name: &str) -> String {
format!("Processed: {}", name)
}
#[cfg(test)]
mod tests {
use super::*;
use predicates::prelude::*;
#[test]
fn test_calculate() {
let result = calculate(3, 4);
// Using predicate directly
assert!(predicates::eq(7).eval(&result));
// Combining predicates
let is_valid = predicates::gt(0).and(predicates::lt(100));
assert!(is_valid.eval(&result));
}
#[test]
fn test_process_name() {
let result = process_name("Alice");
// String predicates
assert!(predicates::str::starts_with("Processed:").eval(&result));
assert!(predicates::str::contains("Alice").eval(&result));
}
}
fn main() {
// Run basic tests
let result = calculate(3, 4);
assert!(predicates::eq(7).eval(&result));
println!("Test passed: 3 + 4 = 7");
let name = process_name("Bob");
assert!(predicates::str::starts_with("Processed:").eval(&name));
println!("Test passed: {}", name);
}use predicates::prelude::*;
struct Config {
port: u16,
host: String,
debug: bool,
}
impl Config {
fn new(port: u16, host: &str, debug: bool) -> Self {
Config {
port,
host: host.to_string(),
debug,
}
}
fn is_valid(&self) -> bool {
// Port should be between 1 and 65535
let valid_port = predicates::ge(1u16).and(predicates::le(65535u16));
// Host should not be empty and contain valid chars
let valid_host = predicates::str::is_empty().not()
.and(predicates::str::is_match(r"^[\w.-]+$").unwrap());
valid_port.eval(&self.port) && valid_host.eval(&self.host)
}
}
fn main() {
let valid_config = Config::new(8080, "localhost", true);
println!("Valid config: {}", valid_config.is_valid());
let invalid_port = Config::new(0, "localhost", false);
println!("Invalid port: {}", invalid_port.is_valid());
let invalid_host = Config::new(8080, "", false);
println!("Invalid host: {}", invalid_host.is_valid());
}use predicates::prelude::*;
struct User {
username: String,
email: String,
age: u8,
}
impl User {
fn validate(&self) -> Vec<&'static str> {
let mut errors = Vec::new();
// Username: 3-20 chars, alphanumeric + underscore
let valid_username = predicates::str::is_match(r"^[\w]{3,20}$").unwrap();
if !valid_username.eval(&self.username) {
errors.push("Username must be 3-20 alphanumeric characters");
}
// Email: basic pattern
let valid_email = predicates::str::is_match(r"^[\w.+-]+@[\w.-]+\.\w+$").unwrap();
if !valid_email.eval(&self.email) {
errors.push("Invalid email address");
}
// Age: 13-120
let valid_age = predicates::ge(13u8).and(predicates::le(120u8));
if !valid_age.eval(&self.age) {
errors.push("Age must be between 13 and 120");
}
errors
}
}
fn main() {
let valid_user = User {
username: "alice_123".to_string(),
email: "alice@example.com".to_string(),
age: 25,
};
let invalid_user = User {
username: "ab".to_string(), // too short
email: "not-an-email".to_string(),
age: 10, // too young
};
println!("Valid user errors: {:?}", valid_user.validate());
println!("Invalid user errors: {:?}", invalid_user.validate());
}use predicates::prelude::*;
struct ApiResponse {
status: u16,
body: String,
headers: Vec<(String, String)>,
}
impl ApiResponse {
fn is_success(&self) -> bool {
predicates::ge(200u16).and(predicates::lt(300u16)).eval(&self.status)
}
fn is_client_error(&self) -> bool {
predicates::ge(400u16).and(predicates::lt(500u16)).eval(&self.status)
}
fn has_content_type(&self, content_type: &str) -> bool {
let predicate = predicates::str::contains(content_type);
self.headers.iter().any(|(key, value)| {
key.to_lowercase() == "content-type" && predicate.eval(value)
})
}
fn body_contains(&self, text: &str) -> bool {
predicates::str::contains(text).eval(&self.body)
}
}
fn main() {
let response = ApiResponse {
status: 200,
body: r#"{"status":"ok","data":[1,2,3]}"#.to_string(),
headers: vec![
("Content-Type".to_string(), "application/json".to_string()),
],
};
assert!(response.is_success());
assert!(response.has_content_type("application/json"));
assert!(response.body_contains("status"));
println!("All API response validations passed!");
let error_response = ApiResponse {
status: 404,
body: "Not Found".to_string(),
headers: vec![],
};
assert!(error_response.is_client_error());
println!("Client error detected correctly");
}use predicates::prelude::*;
struct LogEntry {
level: String,
message: String,
}
impl LogEntry {
fn is_error(&self) -> bool {
predicates::str::similar("error")
.or(predicates::str::similar("fatal"))
.eval(&self.level)
}
fn is_warning(&self) -> bool {
predicates::str::similar("warn")
.or(predicates::str::similar("warning"))
.eval(&self.level)
}
fn contains_pattern(&self, pattern: &str) -> bool {
predicates::str::is_match(pattern)
.unwrap()
.eval(&self.message)
}
}
fn analyze_logs(logs: &[LogEntry]) -> (usize, usize, usize) {
let errors = logs.iter().filter(|log| log.is_error()).count();
let warnings = logs.iter().filter(|log| log.is_warning()).count();
let db_issues = logs.iter()
.filter(|log| log.contains_pattern(r"(?i)database|db|sql"))
.count();
(errors, warnings, db_issues)
}
fn main() {
let logs = vec![
LogEntry { level: "INFO".into(), message: "Server started".into() },
LogEntry { level: "WARNING".into(), message: "High memory usage".into() },
LogEntry { level: "ERROR".into(), message: "Database connection failed".into() },
LogEntry { level: "FATAL".into(), message: "Out of memory".into() },
LogEntry { level: "INFO".into(), message: "DB query executed".into() },
];
let (errors, warnings, db_issues) = analyze_logs(&logs);
println!("Errors: {}", errors);
println!("Warnings: {}", warnings);
println!("DB related: {}", db_issues);
}use predicates::prelude::*;
use std::path::Path;
fn validate_config_file(path: &Path) -> Vec<String> {
let mut issues = Vec::new();
// Check file exists
let exists = predicates::path::exists();
if !exists.eval(path) {
issues.push(format!("File does not exist: {:?}", path));
return issues;
}
// Check it's a file
let is_file = predicates::path::is_file();
if !is_file.eval(path) {
issues.push("Not a regular file".to_string());
return issues;
}
// Read and validate content
if let Ok(content) = std::fs::read_to_string(path) {
// Must contain required sections
let has_required = predicates::str::contains("[server]")
.and(predicates::str::contains("[database]"));
if !has_required.eval(&content) {
issues.push("Missing required sections".to_string());
}
// Must have port specified
let has_port = predicates::str::is_match(r"(?m)^port\s*=").unwrap();
if !has_port.eval(&content) {
issues.push("Port not specified".to_string());
}
}
issues
}
fn main() {
// This would validate actual files in a real scenario
println!("File validation functions ready");
// Quick check that predicates work
let has_config = predicates::str::contains("[server]");
let sample_config = "[server]\nport = 8080";
assert!(has_config.eval(sample_config));
println!("Config validation works!");
}// This shows how predicates integrates with assert_cmd for CLI testing
// In a real project, add: assert_cmd = "2" to Cargo.toml
use predicates::prelude::*;
fn main() {
// Simulate command output validation
let stdout = "Processing complete\n3 items processed\n";
let stderr = "";
// Check stdout
let success_output = predicates::str::contains("complete")
.and(predicates::str::contains("3 items"));
assert!(success_output.eval(stdout));
// Check stderr is empty
let no_errors = predicates::str::is_empty();
assert!(no_errors.eval(stderr));
println!("Command output validation passed!");
// Exit code validation
let exit_code = 0;
assert!(predicates::eq(0).eval(&exit_code));
println!("Exit code validation passed!");
}use predicates::prelude::*;
fn main() {
let predicate = predicates::gt(5).and(predicates::lt(10));
// Display the predicate
println!("Predicate: {}", predicate);
// Find why it fails
let result = predicate.find_case(false, &12);
match result {
Some(case) => {
println!("Found case: {}", case);
}
None => {
println!("No case found (predicate was true)");
}
}
// Tree representation
println!("\nPredicate tree:");
println!("{:?}", predicate);
}predicates::eq(value) creates an equality predicatepredicates::gt(), lt(), ge(), le() for ordering comparisonspredicates::str::contains(), starts_with(), ends_with() for stringspredicates::str::is_match(regex) for regex patternspredicate.and(other) combines with AND logicpredicate.or(other) combines with OR logicpredicate.not() negates a predicatepredicates::iter::contains() and any()/all() for collectionspredicates::path::exists(), is_file(), is_dir() for pathspredicates::option::some() and predicates::result::ok() for Option/ResultPredicate trait for custom predicatespredicate.eval(&value) to test a valueassert_cmd for CLI testing