Frankly, I have a hard time trying figuring out how the validator is supposed to work. Either the code needs a lot of documentation or it requires comprehensive redesigning.
I don't understand the purpose of the toFirstError
methods. The names suggest that they perform a validation action, but instead they are just static replacements for the constructors. The purpose of having static initialization methods instead of direct constructors, is to allow the initializer to return a different type depending on the parameters. You're not doing that so they are just confusing now.
I get a feeling that you are trying to mimic Java streams with the validator. But you end up loading responsibilities about converting and processing the input into the validator. This requires you to maintain state in the validator. All this makes the validator unnecessarily complex and may case loss of reusability. Is the ability to make your code look like stream operations worth it? Can you efficiently use the validator as part in a regular Java stream processing?
Defining each validation criteria as a predicate helps in testing but at the moment each component that requires validation has to know the correct combination of predicates in order to successfully validate an object. You're better of creating a single composite predicate for each class, in which case having the ability to define the validation as a list of predicates becomes a bit useless.
Regarding your comment about annotation driven development, you are placing validation code into the business objects with the methods that return ValidationResult
. You could as well do that with the Bean Validation API I asked about. Your approach is no better, you are still binding your business objects to a single concrete validation library. If you want to use some other way to validate your objects, you then have a lot of useless validation code lying around in your classes. Functionally it's no different and you wouldn't be trying to reinvent the wheel. Whether it is annotation or your own code, the amount of checks is equal and with a well known library you don't force others to learn new undodumented libraries.
Personally I do not like having validation code in business objects, whether it is Bean Validation API or home grown stuff. It breaks the single responsibility principle. I prefer having validators be separate classes that implement validation rules for a specific class for a specific use case. I have been enough times in a place where I would have wanted to reuse a common data class but have not been able to do so because they have been riddled with validation rules invented by the person who created the first use case.
Can you report all validation errors in one pass? The code isn't very straightforward but I get the impression that you stop at the first one, right? Not being able to list all validation errors immediately would be a complete deal braker.