- https://softwaremill.com/javaslang-data-validation/
- https://www.youtube.com/watch?v=wJn4LHD6Y7o
- https://www.vavr.io/vavr-docs/#_validation
- https://github.com/mtumilowicz/java11-vavr-validation
- https://github.com/mtumilowicz/java11-jsr303-custom-validation
- in the workshop we will try to rewrite methods in the classes with naming convention:
com.example.vavr.validation.workshop.*.*Workshopusing hints and refactoring plan depicted below - answers:
com.example.vavr.validation.workshop.*.*Answer
- change in
build.gradletotest { exclude '**/**AfterRefactor*' }and we want to fix all failing teststest { exclude '**/**BeforeRefactor*' } - rewrite
patternsusingValidationinstead of throwing exception- method:
validateWorkshopin classesAgeCityEmailNamePostalCode
- method:
- rewrite
*Workshopclasses usingValidationinstead of throwing exception and try-catch blocks (more hints in the classes)NewAddressRequestValidatorWorkshopNewPersonRequestValidatorWorkshopPersonControllerWorkshop
- delete
ErrorMessagesControllerAdviceValidationException
- all tests
**/*WorkshopAfterRefactor*should pass in this step
Validationis an applicative functor and facilitates accumulating errors- when trying to compose Monads, the combination process will short circuit at the first encountered error as getting subsequent wrapped values depends on results of previous calculations
Validationwill continue processing and accumulate all errors (if any)- it is especially useful when doing validation of multiple fields, say a web form, and you want to know all errors encountered, instead of one at a time
- contrary to Bean Validation standard (JSR-303 and JSR-349):
- https://github.com/mtumilowicz/java11-jsr303-custom-validation
- https://github.com/mtumilowicz/java-bean-validation2
- we do not need any infrastructure providers (AOP, dynamic proxies, DI)
- bad request is not rejected and can be easily handled in a reasonable way (for example send to patch service)
interface Validation<E, T> extends Value<T>, Serializableinterface Value<T> extends Iterable<T>
- two implementations:
final class Valid<E, T> implements Validation<E, T>- contains valid datafinal class Invalid<E, T> implements Validation<E, T>- is an error representation
- creating
static <E, T> Validation<E, T> valid(T value)static <E, T> Validation<E, T> invalid(E error)- example
private static final IntPredicate PREDICATE = i -> i > 0; public static Validation<String, Age> validateAnswer(int age) { return PREDICATE.test(age) ? Validation.valid(new Age(age)) : Validation.invalid("Age: " + age + " is not > 0"); }
- combining
static <E, T1, ...> Builder<E, T1, ...> combine(Validation<E, T1> validation1, Validation<E, T2> validation2, ...)public <R> Validation<Seq<E>, R> ap(FunctionN<T1, T2, ..., R> f)- example
static Validation<Seq<String>, NewAddressCommand> validate(NewAddressRequest request) { return Validation .combine( City.validateAnswer(request.getCity()), PostalCode.validateAnswer(request.getPostalCode())) .ap((city, postalCode) -> NewAddressCommand.builder() .city(city) .postalCode(postalCode) .build()); } - up to 8 arguments
combineandfunctionNinapshould have the same number of params- valid type of
combineparams should be the same as type of arguments offunctionNinap combineparams order corresponds to the order of params in thefunctionNinap- if all of
ValidationsincombineareValid, theap(f)method maps all results to a single value using a functionf - if at least one of
ValidationsisInvalid, thenap(f)returnsInvalidwith all errors aggregated - for example, for 3 arguments
aplooks likeapinBuilderclass (Builderis returned bycombinemethod)public <R> Validation<Seq<E>, R> ap(Function3<T1, T2, T3, R> f) { return v3.ap(v2.ap(v1.ap(Validation.valid(f.curried())))); }apinValidationclass
default <U> Validation<Seq<E>, U> ap(Validation<Seq<E>, ? extends Function<? super T, ? extends U>> validation) { Objects.requireNonNull(validation, "validation is null"); if (isValid()) { if (validation.isValid()) { final Function<? super T, ? extends U> f = validation.get(); final U u = f.apply(this.get()); return valid(u); } else { final Seq<E> errors = validation.getError(); return invalid(errors); } } else { if (validation.isValid()) { final E error = this.getError(); return invalid(List.of(error)); } else { final Seq<E> errors = validation.getError(); final E error = this.getError(); return invalid(errors.append(error)); } } }
- consuming
- pattern matching
return Match(validation).of( Case($Valid($()), ...), Case($Invalid($()), ...) ); - folding to single value
return validation.fold(..., ....)U fold(Function<? super E, ? extends U> fInvalid, Function<? super T, ? extends U> fValid)
- pattern matching
- transforming
- mapping
validation.map(...) .mapError(...)Validation<E, U> map(Function<? super T, ? extends U> f)Validation<U, T> mapError(Function<? super E, ? extends U> f)
- bimapping
validation.bimap(..., ...)Validation<E2, T2> bimap(Function<? super E, ? extends E2> errorMapper, Function<? super T, ? extends T2> valueMapper)
bimapvsfold-bimapreturnsValidationandfoldreturns any arbitrary type
- mapping