Is there a way to add variables in Dropwizard's validation error message? Something in the effect of
#ValidationMethod(message=String.format("Url cannot be null, field value = %s", fieldValue))
public boolean isNotValid() {
String fieldValue = this.getFieldValue();
return this.url == null;
}
I just want to add variable into the error message.
I found an answer. Hibernate 5.1 has error message interpolation which kinda takes care of this.
#Size(min = 0, max = 0, message="${validatedValue} is present"))
public String getErrorMessage() {
List<String> illegalValues = ImmutableList.of("illegal value");
return illegalValues;
}
It's a little hacky, but it solves the problem. Take a look at http://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-message-interpolation.html
I found a proper way to validate beans - don't use dropwizard's #ValidationMethod, but instead define your own annotation and validator:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = MyValidator.class})
#Documented
public #interface ValidPerson {
String message() default "Bad person ${validatedValue.name}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class MyValidator implements ConstraintValidator<ValidPerson,Person> {
#Override
public void initialize(ValidPerson annotaion) {}
#Override
public boolean isValid(Person p, ConstraintValidatorContext context) {
return false; //let's say every person is invalid :-)
}
}
I'm unfamiliar with Dropwizard, but Java's annotations are simply compile-time metadata. You can't call methods in annotation declarations simply because the Java compiler does not perform the same compile-time code execution as some other compilers, such as C or C++.
Related
For a SpringBoot project I'm using custom validation using custom annotations.
In particular, I have a lot of enums and I would like to validate if into the inputs there are correct enums values (e.g. from json to DTO objects)
I'm using this approach:
I defined my annotation:
#Target( { FIELD, PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = PlatformValidator.class)
public #interface PlatformValidation {
String message() default "Invalid platform format";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and my validator:
public class PlatformValidator implements ConstraintValidator<PlatformValidation, String>
{
public boolean isValid(String platform, ConstraintValidatorContext cxt) {
try {
if (!isNull(platform)) {
Platform.valueOf(platform);
}
return true;
} catch (Exception ex) {
return false;
}
}
}
My question is, can I have a generic enum validation annotation and specify the enum that I need as a parameter of my annotation?
Something like #MyValidation(enum=MyEnum, message= ...)
For the generics approach is use Java reflection. So the annotation should be:
#EnumValidation(enum=YourEnum.class, message=...)
The tutorial about reflection: Jenkov's Tutorial
In the domain model object I have the following field:
private TermStatus termStatus;
TermStatus is an enum:
public enum TermStatus {
NONE,
SUCCESS,
FAIL
}
In the DTO, I have the same field as in the domain object. The question is, how can I validate the passed value? If the API client now passes an incorrect string with the enum value as a parameter (for example, nOnE), it will not receive any information about the error, only the status 400 Bad Request. Is it possible to validate it like this, for example, in the case of javax.validation annotations like #NotBlank, #Size, where in case of an error it will at least be clear what it is. There was an idea to make a separate mapping for this, for example "items/1/complete-term" instead of direct enum transmission, so that in this case the server itself would set the SUCCESS value to the termStatus field. But as far as I know, these things don't look very good in REST API, so I need your ideas
Instead of validating enum directly, you could check whether String is valid for specific enum. To achieve such an effect you could create your own enum validation annotation.
#Documented
#Constraint(validatedBy = EnumValidatorConstraint.class)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#NotNull
public #interface EnumValidator {
Class<? extends Enum<?>> enum();
String message() default "must be any of enum {enum}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then you need to implement validator to check whether String exist as a part of this enum.
public class EnumValidatorConstraint implements ConstraintValidator<EnumValidator, String> {
Set<String> values;
#Override
public void initialize(EnumValidator constraintAnnotation) {
values = Stream.of(constraintAnnotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toSet());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return values.contains(value);
}
}
Lastly, you need to annotate your field with #EnumValidator.
#EnumValidator(enum = TermStatus.class)
private String termStatus;
In case of not matching String a MethodArgumentNotValidException will be thrown. Same as for #NotNull or other constraint validation.
Sounds like you need to implement your own response after validation and tell the API client that the data in your received DTO is invalid and return message with the actual received value (nOnE in your case) and maybe the list of your valid values (if that's not gonna be a security concern). Also, I think the ideal http status for your response would be 422 instead of a generic 400 Bad Request.
For your actual validation implementation, I think you can just directly compare the converted value from DTO to ENUM of the data you received from the API client against your ENUM values in the back-end. If equals to any of the ENUM values, then it's a valid request (200) else, 422.
Hope this helps!
You can make a utility method inside your enum like below
private String text;
TermStatus(String text) {
this.text = text;
}
public static TermStatus fromText(String text) {
return Arrays.stream(values())
.filter(bl -> bl.text.equalsIgnoreCase(text))
.findFirst()
.orElse(null);
}
And set value in dto like below
dto.setTermStatus(TermStatus.fromText(passedValue))
if(dto.getTermStatus()== null)
throw new Exception("Your message");
Hope this helps!
You should use String data type for termStatus. Because of client sends String value for this. Then you have to create Custom validation constraints to fix this as below.
ValueOfEnum
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = ValueOfEnumValidator.class)
public #interface ValueOfEnum
{
Class<? extends Enum<?>> enumClass();
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ValueOfEnumValidator
public class ValueOfEnumValidator implements ConstraintValidator<ValueOfEnum, CharSequence>
{
private List<String> acceptedValues;
#Override
public void initialize(ValueOfEnum annotation)
{
acceptedValues = Stream.of(annotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toList());
}
#Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context)
{
if (value == null) {
return true;
}
return acceptedValues.contains(value.toString());
}
}
Now you can #ValueOfEnum annotation for your domain model. Then add #Validated annotation in front of your controller class domain object.
#ValueOfEnum(enumClass = TermStatus.class, message = "Invalid Term Status")
private String termStatus;
I need to disable validation if the value of email is null and check on it if the value is not null.
#Email(message = "{invalidMail}")
private String email;
I found the answer and it's that almost all java validation annotations accepts null so if my value is null it's going to accept it otherwise it will check.
You can't achieve this result with the predefined set of validation annotations only.
You have to create a custom validation annotation which performs the validation based on the specifications. You can get inspired on the Baeldung's article Spring MVC Custom Validation.
Here is the annotation.
#Documented
#Constraint(validatedBy = MyEmailValidator.class) // Class which performsthe validation
#Target({ ElementType.FIELD }) // Applicable to a field
#Retention(RetentionPolicy.RUNTIME)
public #interface MyEmail {
String message() default "The email is invalid"; // The default message
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And a class which actually performs the validation itself against the email Regex if the input is not null. Returns true otherwise as null is accepted. Note this class has to implement ConstraintValidator<A extends Annotation, T>.
public class MyEmailValidatorimplements ConstraintValidator<MyEmail, String> {
// Email Regex
private final String emailPattern= "[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}";
#Override
public void initialize(MyEmail myEmail) { }
#Override
public boolean isValid(String input, ConstraintValidatorContext cxt) {
if (input == null) {
return true;
} else return Pattern.matches(emailPattern, input);
}
}
I am trying to extend the behavior of the #NotBlank constraint to apply to URIs by making a custom constraint called #NotBlankUri.
Here's my constraint annotation:
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
public #interface NotBlankUri {
String message() default "{project.model.NotBlankUri.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and here is the ConstraintValidator:
public class NotBlankUriValidator implements ConstraintValidator<NotBlankUri, URI> {
public void initialize(NotBlankUri annotation) {
}
public boolean isValid(URI uri, ConstraintValidatorContext context) {
NotBlankValidator nbv = new NotBlankValidator();
return nbv.isValid(uri.toString(), context);
}
}
Problem is that the isValid() method on the ConstraintValidator is getting null values for the URI argument. I thought this wasn't supposed to happen given the fact that #NotBlank itself is annotated #NotNull. That not being the case, I tried adding #NotNull as a meta-annotation to my #NotBlankUri, but that didn't have the desired effect either. How can I make my annotation constraint behave like #NotBlank, which seems to be stacking on top of the behavior of #NotNull?
As per the documentation, you can't use the #NotBlank annotation on a datatype that is not a String.
public #interface NotBlank
Validate that the annotated string is not null or empty. The difference to NotEmpty is that trailing whitespaces are getting ignored.
So if you declared your validator to validate a String, everything would be fine and you could write your annotation like this:
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
#NotBlank
public #interface NotBlankUri {
String message() default "{project.model.NotBlankUri.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
If you are deadset on using the URI class 1 you need to perform custom validation logic yourself like this:
Annotation:
#NotNull(message="URI must not be null")
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
public #interface NotBlankUri {
String message() default "URI must not be blank";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator:
public class NotBlankUriValidator implements ConstraintValidator<NotBlankUri, URI> {
public void initialize(NotBlankUri annotation) {
}
public boolean isValid(URI uri, ConstraintValidatorContext context) {
boolean isValid = true;
System.out.println("URI: " + uri);
//Leave null checks to your #NotNull constraint.
//This is only here to prevent a NullPointerException on the next check.
if(uri == null){
return true;
}
if(uri.toString().isEmpty()){
isValid = false;
}
return isValid;
}
}
I ran the above with a test harness:
public class UriContainer {
public UriContainer(URI uri){
this.uri = uri;
}
#NotBlankUri
private URI uri;
public URI getUri() {
return uri;
}
}
public static void main(String[] args) throws URISyntaxException{
UriContainer filledContainer = new UriContainer(new URI("Stuff"));
UriContainer emptyContainer = new UriContainer(new URI(""));
UriContainer nullContainer = new UriContainer(null);
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<UriContainer>> filledViolations = validator
.validate(filledContainer);
Set<ConstraintViolation<UriContainer>> emptyViolations = validator
.validate(emptyContainer);
Set<ConstraintViolation<UriContainer>> nullViolations = validator
.validate(nullContainer);
System.out.println("Filled: ");
filledViolations.stream().forEach(System.out::println);
System.out.println("Empty: ");
emptyViolations.stream().forEach(System.out::println);
System.out.println("Null: ");
nullViolations.stream().forEach(System.out::println);
}
which output the following violations:
URI: Stuff
URI:
URI: null
Filled:
Empty:
ConstraintViolationImpl{interpolatedMessage='URI must not be blank', propertyPath=uri, rootBeanClass=class sandbox.UriContainer, messageTemplate='URI must not be blank'}
Null:
ConstraintViolationImpl{interpolatedMessage='URI must not be null', propertyPath=uri, rootBeanClass=class sandbox.UriContainer, messageTemplate='URI must not be null'}
As you can see, this allows you to output different error messages based on if the URI is blank or null. Just make sure if you are using a javax.validation annotation you check which datatype you operate on.
1: which by the way, performs validation when you construct the object, and will throw a URISyntaxException if the String passed to the constructor violates RFC 2396
I am trying to use spring to check user online input to ensure that the two characters they enter is an actual US state, is there any way of doing this, hopefully using a preset pattern? like, #State or something (if that was a legit annotation). Also, is there a good annotation commonly used for a String street, and String city field? That is other than #NotNull and #NotEmpty
Any help would be greatly appreciated!!
Unfortunately there is no out of the box however you can create your own #State annotation , all you need is to define your annotation and class implementing ConstraintValidator(which handles the validation logic) E.g.
#Constraint(validatedBy = StateConstraintValidator.class)
#Target( { ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface State {
String message() default "{State}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class StateConstraintValidator implements ConstraintValidator<String, String> {
private static final Set<String> CODE_MAP = new HashSet<>(){
{add("AR");}
{add("AK");} //add more codes ...
};
#Override
public void initialize(String state) { }
#Override
public boolean isValid(String value, ConstraintValidatorContext cxt) {
if(value == null) {
return false;
}
return CODE_MAP.contains(value);
}
}
In the similar manner you can create other annotations.