2 releases

0.1.1 Jan 15, 2024
0.1.0 Jan 4, 2024

#322 in Data structures

26 downloads per month

MIT/Apache

61KB
1K SLoC

Are you a rusty little thing?

Do you want to write 10,000-line functions and just chain iterators forever?

Do you like to watch the compiler cry while it's trying to figure out WTF is a Fold<Map<Filter<TakeWhile<Map<StepBy<Zip<SkipWhile...

Do you get insecure about your ugly, disgusting, imperative code when you're next to Haskellers?

Introducing...

validiter

validiter is meant to provide a nice rusty api for performing validations on iterators. Here is a very simple example for an adapter it provides:

(0..10).validate().at_most(7) // wraps the first 7 elements in Ok(i32) and the rest in a Err(ValidErr::TooMany(i32))

Unfortunately, our beautiful and pure functions are often fouled by unsanitized data. So how can we allow data preprocessing to be integrated into a large, LAZY iterator chain? This is where validiter comes to help. All in all, this crate is pretty simple - take an iterator, and declare that you want to validate it. There are 2 ways to do so:

  1. Import validiter::Unvalidatable to your scope, and call validate() on said iterator - you can now call all of the ValidIter type adapters.
  2. A little more scuffed, but also valid - if your iterator is already yielding results, you can map them to ValidIter::ValidErr<the-type-you-want>::Mapped, then import validiter::ErrLiftable into your scope and call lift_errs on the iterator - this too will allow you to call ValidIter methods.

We have examples for both of these methods. We'll start with a simple one, using the validate method (this is multi_validated_iterator in the examples folder):

use validiter::{Unvalidatable, ValidIter};

fn main() {
    // This is the standard way to use validiter - call validate on
    // some 'Unvalidatable' iterator, and then place restrictions
    // on the iteration. Notice that 'ValidErr' type errors are always
    // ignored by validiter adapters, so the order of validation
    // placement matters, if the iteration fails - there might be 
    // ignored errors, on elements that already failed a different
    // validation.
    (0..10)
        .validate()
        .at_most(7)
        .between(2, 8)
        .ensure(|i| i % 2 == 0)
        .at_least(4)
        .for_each(|v| println!("{:?}", v));
}

The second example is a bit more involved, using the lift_errs method (numeric_csv_parsing in the examples folder):

use validiter::{ErrLiftable, ValidErr, ValidIter};

fn main() {
    // In this example we will use the 'lift_errs' method to
    // create a 'Vec<Vec<f64>>' collection, while ensuring 
    // the mathematical validity if this collection as a numerical
    // matrix. We will also force the matrix to be non-negative,
    // just for funsies.

    // this is a CSV format str, with 2 rows and 2 columns
    let csv = "1.2, 3.0
                4.2, 0.5";

    // we'll use iterator methods on the CSV to build an actual 
    // split the csv by rows/lines
    let mat = csv        .lines()
        // convert each row to a matrix row
        .map(
            |line| {
                // split by elements
                line.split(",")
                    // map the elements to f64 values
                    .map(|s| s.trim())
                    // if we get a parse error, we want to map it to our own error types - ValidErr<f64>
                    .map(|s| s.parse::<f64>().map_err(|_| ValidErr::<f64>::Mapped))
                    // because 'Map' is not a 'ValidIter', we need to convert the underlying data structure type
                    .lift_errs() // the iterator is over VResult<f64>, but map is not a ValidIter!
                    // force non-empty rows
                    .at_least(1)
                    // simple 'greater than 0' validation
                    .ensure(|f| *f >= 0.0)
                    // collecting each row to a vector, but now Ok Type is a vector, but Err Type is f64!
                    .collect::<Result<Vec<f64>, ValidErr<f64>>>()
            },
        )
        // we use lift_errs again to fix the typing issues
        .lift_errs()
        // force non-empty matrix
        .at_least(1)
        // force equal-sized rows
        .const_over(|vec| vec.len())
        // collect into a matrix
        .collect::<Result<Vec<_>, _>>();
    assert_eq!(mat, Ok(vec![vec![1.2, 3.0], vec![4.2, 0.5]]));
    print!("{:?}", mat)
}

Most of the documentation for this crate is just the docstrings for the various methods of the ValidIter trait, so that's (probably) the place to go if you're unsure about what some validation adapter does.

The things that validiter does not (yet) support and you might want, but won't get, are:

I'd love to hear suggestions about anything - ESPECIALLY if you think I'm doing something wrong in the definition or implementation of this crate.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~175KB