predicates-rs

repository·master·Indexed 42 days ago

https://github.com/assert-rs/predicates-rs

A Rust implementation of boolean-valued predicate functions providing a toolkit for building and composing complex logical conditions. Version 3.1.4 includes the predicates-core crate for foundational traits and the predicates-tree crate for visualizing evaluation logic as a tree structure. It features a composable first-order predicate trait, support for type erasure via BoxPredicate, and specialized predicates for floating-point proximity, membership checks, and custom function wrapping.

Tokens
9.3K
Snippets
36
Records
47
Agent score
52%

What's inside predicates-rs

  1. Import predicates into your crate

    master

    After adding the dependency, import the library and its prelude to access the predicate functions. The prelude provides common traits and functions for building boolean-valued predicates.

    extern crate predicates;
    
    use predicates::prelude::*;
  2. How Predicates work

    master

    A Predicate is a boolean-valued function of one argument. The core of the library is the Predicate<T> trait, which requires an eval method that takes a reference to an item of type T and returns a bool.

    Predicates are highly composable. You can create them using pre-made functions, wrap existing functions, or implement the Predicate trait yourself. Once created, you can combine them using logical combinators like and, or, and not.

    Implementing a custom Predicate

    To implement a custom predicate, implement the Predicate<T> trait. For better integration (like failure reporting), it is also recommended to implement PredicateReflection and fmt::Display.

    use std::fmt;
    use predicates::prelude::*;
    
    struct IsTheAnswer;
    
    impl Predicate<i32> for IsTheAnswer {
        fn eval(&self, variable: &i32) -> bool {
            *variable == 42
        }
    }
    
    impl predicates::reflection::PredicateReflection for IsTheAnswer {}
    
    impl fmt::Display for IsTheAnswer {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "var.is_the_answer()")
        }
    }
    
    // Usage
    assert_eq!(true, IsTheAnswer.eval(&42));
    use std::fmt;
    
    use predicates::prelude::*;
    
    struct IsTheAnswer;
    impl Predicate<i32> for IsTheAnswer {
        fn eval(&self, variable: &i32) -> bool {
            *variable == 42
        }
    }
    impl predicates::reflection::PredicateReflection for IsTheAnswer {}
    impl fmt::Display for IsTheAnswer {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "var.is_the_answer()")
        }
    }
    
    assert_eq!(true, IsTheAnswer.eval(&42));
    let almost_the_answer = IsTheAnswer.or(predicate::in_iter(vec![41, 43]));
    assert_eq!(true, almost_the_answer.eval(&41));
  3. Use `FileContentPredicate` to bridge byte predicates and file paths

    master

    The FileContentPredicate<P> is an adapter that allows a predicate P (where P: Predicate<[u8]>) to be evaluated against a std::path::Path.

    When eval is called on a FileContentPredicate:

    1. It attempts to read the file at the provided path into a byte buffer.
    2. If the file is read successfully, it evaluates the inner predicate P against that buffer.
    3. If the file cannot be read, the predicate returns false (it does not panic, but swallows the I/O error during eval).

    This adapter is typically instantiated via the from_file_path() method on any predicate that implements Predicate<[u8]>.

  4. Combine predicates using logical combinators

    master

    The Predicate trait provides methods to combine multiple predicates into a single logical expression:

    • pred_a.and(pred_b): Returns true if both predicates succeed.
    • pred_a.or(pred_b): Returns true if one or both predicates succeed.
    • pred_a.not(): Returns true if the predicate fails.
  5. What are predicates and how are they used

    master

    A predicate is a boolean-valued function of one argument. The predicates-rs library provides a composable first-order predicate trait that allows you to create and assemble combinatorial logic at runtime.

    This is particularly useful for building dynamic filters and checks that can be modified via user interaction. By using predicates, you can separate the configuration logic (which builds the predicate) from the execution logic (which uses the predicate to filter or evaluate values), ensuring the execution code remains agnostic of the specific configuration.

  6. Use the predicates prelude

    master

    The prelude module provides access to all extension traits and the predicate module, which covers the majority of common use cases. Importing the prelude is the recommended way to start using the library.

    use predicates::prelude::*;
  7. Convert binary file predicates to string predicates with utf8()

    master

    The BinaryFilePredicate struct provides a .utf8() method that attempts to transform the predicate into a StrFilePredicate.

    • Returns: Option<StrFilePredicate>
    • Behavior: If the underlying binary content of the reference file is valid UTF-8, it returns Some(StrFilePredicate). If the content is not valid UTF-8, it returns None.
    // Example of converting a binary predicate to a string predicate
    let predicate_file = predicate::path::eq_file(Path::new("Cargo.toml")).utf8().unwrap();
  8. Match file contents using `from_file_path()`

    master

    You can adapt a predicate that operates on byte slices ([u8]) to operate on file contents by using the from_file_path() extension method. This creates a FileContentPredicate which reads the file at a given path and applies the underlying predicate to the resulting byte buffer.

    To use this, ensure your predicate chain ends with a predicate compatible with [u8] (like predicate::str::is_empty() after a UTF-8 conversion) and then call .from_file_path().

    use predicates::prelude::*;
    use std::path::Path;
    
    // Create a predicate that checks if file content is NOT empty
    let predicate_fn = predicate::str::is_empty().not().from_utf8().from_file_path();
    
    // Evaluate against a file path
    assert_eq!(true, predicate_fn.eval(Path::new("./tests/hello_world")));
    assert_eq!(false, predicate_fn.eval(Path::new("./tests/empty_file")));
  9. Wrap predicates in a `BoxPredicate` using `.boxed()`

    master

    Use the .boxed() method (provided by the PredicateBoxExt trait) to wrap a predicate in a BoxPredicate. This converts the predicate into a trait object, which is useful for:

    • Type Erasure: The type of BoxPredicate remains constant even if the underlying predicate logic changes (e.g., adding or removing steps).
    • Collections: It provides a common type that allows you to store different predicate implementations in a single collection, such as a Vec<BoxPredicate<T>>.
    • Standard Traits: BoxPredicate automatically implements Debug and Display.

    Note: The predicate must implement Send, Sync, and 'static to be boxed.

    use predicates::prelude::*;
    
    let predicates = vec![
        predicate::always().boxed(),
        predicate::never().boxed(),
    ];
    
    assert_eq!(true, predicates[0].eval(&4));
    assert_eq!(false, predicates[1].eval(&4));