I'm trying to create a composite validator for a set of APIs. The generic interface is:
public interface APIValidator<Input, Context> {
boolean validate(Input i, Context c);
}
For example, if there are 2 APIs: Send and Accept, I have "marker" interfaces:
public interface SendAPIValidator implements APIValidator<SendInput, SendContext> {
boolean validate(SendInput i, SendContext c);
}
public interface AcceptAPIValidator implements APIValidator<AcceptInput, AcceptContext> {
boolean validate(AcceptInput i, AcceptContext c);
}
And these, respectively, have implementations.
Then I have a static class to create a composite validator:
class Validators {
public static <I, C> APIValidator<I, C> composite(Set<APIValidator<I, C>> validators) {
return (input, context) -> {
return validators.stream()
.allMatch(validator -> validator.validate(input, context));
}
}
However, trying to inject this into a provider or something doesn't work:
SendAPIValidator provideCompositeValidator(Set<SendAPIValidator> validators) {
// Error: no instance(s) of type variable(s) C, I exist so that SendAPIValidator conforms to APIValidator<I, C>
return Validators.compositeValidator(validators);
}
Is this because of Java's type invariants (I forget the technical term)? I know it's possible to do this if I pass in APIValidator<SendInput, SendContext>
instead. Is there another way?
As explained in the comments, you have to declare the input parameter with a bounded wildcard (see PECS) to make it compatible with Set<SendAPIValidator>
:
public static <I, C> APIValidator<I, C> composite(Set<? extends APIValidator<I, C>> validators) {
return (input, context) -> validators.stream()
.allMatch(validator -> validator.validate(input, context));
}
The other problem is that this method returns an instance of APIValidator
which is neither assignable nor castable to SendAPIValidator
. However, since they share the same functional signature, you can can easily convert it to the desired type with a method reference:
SendAPIValidator provideCompositeValidator(Set<SendAPIValidator> validators) {
return Validators.composite(validators)::validate;
}