2 releases
0.1.1 | Jan 15, 2024 |
---|---|
0.1.0 | Jan 4, 2024 |
#348 in Data structures
99 downloads per month
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:
- Import
validiter::Unvalidatable
to your scope, and callvalidate()
on said iterator - you can now call all of theValidIter
type adapters. - 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 importvaliditer::ErrLiftable
into your scope and calllift_errs
on the iterator - this too will allow you to callValidIter
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:
- Error messages inside the ValidErr type.
- Tight compatability with the
anyhow
crate (https://docs.rs/anyhow/latest/anyhow/).
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
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
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