I'm playing with vavr library in order to handle validation.
My question is how to combine a large number of field related validation to a single one.
I mean, here my Reference
class:
public class Reference {
private final ReferenceUniqueIdentifier referenceUniqueIdentifier;
private final TransactionId transactionId;
private final DocumentId documentId;
private final String field4;
private final String field5;
private final String field6;
private final String field7;
private final String field8;
private final String field9;
private final String field10;
}
I've also implemented a ReferenceValidator
class like this:
public class ReferenceValidator {
public static Validation<String, ReferenceUniqueIdentifier> uniqueIdentifierNonNull(
ReferenceUniqueIdentifier referenceUniqueIdentifier) {
return Optional.ofNullable(referenceUniqueIdentifier)
.map(Validation::<String, ReferenceUniqueIdentifier>valid)
.orElse(Validation.invalid("Reference unique Identifier must be not null"));
}
public static Validation<String, TransactionId> transactionIdentifierNonNull(TransactionId transactionId) {
return Optional.ofNullable(transactionId)
.map(Validation::<String, TransactionId>valid)
.orElse(Validation.invalid("Transaction identifier must be not null"));
}
...
}
From my Reference
class I've implemented a factory method like this:
public Validation<Seq<String>, Reference> of(ReferenceUniqueIdentifier referenceUniqueIdentifier,
TransactionId transactionId) {
Validation<String, ReferenceUniqueIdentifier> uniqueIdentifierNonNullValidation = ReferenceValidator
.uniqueIdentifierNonNull(referenceUniqueIdentifier);
Validation<String, TransactionId> transactionIdNonNullValidation = ReferenceValidator
.transactionIdentifierNonNull(transactionId);
//...How to combine 10 Validation<String, TField> into a single one?
}
My question is, how could I combine my 10 or more relation Validation<Error, ?>
into a single Validation<Error, Reference>
?
Abocve
Normally...
when combining Validation
objects into a new Validation
object you would use Validation.combine
method in combination with the ap
method. However, this combining allows for up to a max of 8
Validation
objects in one go.
So for combining just 2 validations for example, you could do something like:
// ... the initial factory method implementation
return Validation.combine(uniqueIdentifierNonNullValidation, transactionIdNonNullValidation)
.ap(Reference::new); // or use a lambda instead of a method reference
While in this case...
the question is about combinding more than 8
validations, a bit more creativity might be required.
One way could be to first combine the first 5 validations into a Tuple5
and then the next 5 validations into another Tuple5
(or some other split if that makes more sense). And after that, combine the the validated Tuple5
into the desired Reference
object you were looking for, using a similar method as described above.
Something along these lines:
var firstValidatedFields = Validation.combine(
uniqueIdentifierNonNullValidation,
transactionIdNonNullValidation,
documentIdValidation,
field4Validation,
field5Validation
).ap(Tuple5::new);
var secondValidatedFields = Validation.combine(
field6Validation,
field7Validation,
field8Validation,
field9Validation,
field10Validation
).ap(Tuple5::new);
return firstValidatedFields.combine(secondValidatedFields)
// Combine the tuples
.ap((t1, t2) -> new Reference(
t1._1, t1._2, t1._3, t1._4, t1._5,
t2._1, t2._2, t2._3, t2._4, t2._5
))
// Flatten the otherwise nested errors
.mapError(nestedErrors -> nestedErrors.flatMap(Function.identity()));
Alternative
An alternative option could be to look into the constructor arguments of the Reference
objects to check whether some variables could be grouped into nested objects, which could bundle a few of these fields. These nested objects could then be assembled first by combine(...).ap(..)
and then be combined into the final object.
For some classes it totally make sense to have some nested structure and for others it doesn't. You should check for yourself whether it's viable for this specific case.