I created a custom annotation
#Documented
#Constraint(validatedBy = CheckGranularityValidator.class)
#Target( { ElementType.PARAMETER} )
#Retention(RetentionPolicy.RUNTIME)
public #interface CheckGranularity {
String message() default "Duration has to be a multiple of granularity";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
With a validator like so
public class CheckGranularityValidator implements ConstraintValidator<CheckGranularity, AssetCostsRequest> {
#Override
public void initialize(final CheckGranularity constraintAnnotation) {
}
#Override
public boolean isValid(final AssetCostsRequest value, final ConstraintValidatorContext context) {
return value.getRange().getDuration() % value.getGranularity() == 0;
}
}
I tried using it in my RestController
#RestController
public class CalcApiController extends CalcApi {
#Override
public ResponseEntity<String> calcProfitability(#Valid #CheckGranularity #RequestBody final AssetCostsRequest assetCostsRequest) {
return ResponseEntity.ok("Works");
}
I tried using this annotation by writing a test:
#Test
public void calcTest() {
final AssetCostsRequest request = new AssetCostsRequest()
.setRange(new TimeRange(100L, 200L))
.setGranularity(26L);
given()
.contentType(ContentType.JSON)
.body(request)
.when()
.post("/calc")
.then()
.statusCode(HttpStatus.SC_BAD_REQUEST);
}
Relevant part of AssetCostsRequest:
public class AssetCostsRequest {
#JsonProperty
#NotNull
private TimeRange range;
#JsonProperty
#NotNull
private Long granularity = 30L;
...getters & setters
}
Test method returns with 200. When I try to set a breakpoint in isValid method, it isn't hit when I run the test. I tried changing order of annotations, getting rid of #Valid, changing #Target in CheckGranularity class, nothing helped. I'm using RestAssured for testing.
How do I make it, so my annotation is properly validating a parameter?
Change CheckGranularity's target to ElementType.TYPE and add #CheckGranularity directly on AssetCostsRequest. Also remove #CheckGranularity from endpoint definition.
How it works. By adding #Valid on endpoint's parameter you tell spring to validate it. Adding validation like #CheckGranularity won't work on the same level as Valid. It has to be added somewhere inside parameters class.
Related
I have created a custom validator that should validate if the body of a request (a simple string) is in Json format. I see that the custom validator is never called. Here are some parts of my code:
#RequestMapping(value = "/endpoint", method = { RequestMethod.POST })
#ResponseBody
public ResponseEntity<String> authorize(#RequestBody #MyValidator String token) {
// logic
}
This is the annotation:
#Target({ ElementType.PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = { JsonSyntaxValidator.class })
#Documented
public #interface MyValidator {
String message() default "{Token is not in Json syntax}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
This is the validator:
public class JsonSyntaxValidator implements ConstraintValidator<MyValidator, String> {
#Override
public void initialize(JsonFormat constraintAnnotation) {
}
/**
* It returns true if the Google Pay or Apple Pay token is in Json format.
*/
#Override
public boolean isValid(String token, ConstraintValidatorContext constraintValidatorContext) {
boolean isValid = true;
try {
JsonParser.parseString(token);
} catch (JsonSyntaxException e) {
isValid = false;
}
return isValid;
}
}
I have tried invoking the endpoint with postman passing it a string not formatted as json and in debug I see that the check is skipped past.
I don't want to have a POJO with fields, I just want the request body as a string.
I haven't found much online, only a post stating that it might not be possible.
Any help would be really appreciated :)
Your controller should be using the #Valid annotation instead of #MyValidator. I updated your controller below to what should work.
public ResponseEntity<String> authorize(#Valid #RequestBody String token) {
// logic
}
I'm creating a custom method level constraint for a Spring REST method but the validator is not being triggered at all. The isValid method is never called.
//
// Controller
//
#PostMapping("/{id}")
#DTOParametersMatch
public ResponseEntity<DTO> createDTO(
#PathVariable("id") #SuppressWarnings("unused") UUID id,
#Validated(CreateValidation.class) #RequestBody DTO dto
) {
System.out.println("***** createDTO called");
DTO created = dtosService.createDTO(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
//
// DTOParametersMatch
//
#Constraint(validatedBy = DTOParametersMatchValidator.class)
#Target({ METHOD, CONSTRUCTOR })
#Retention(RUNTIME)
#Documented
public #interface DTOParametersMatch {
String message() default "DTO ID in path must match DTO ID in body";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//
// DTOParametersMatchValidator
//
#SupportedValidationTarget(PARAMETERS)
public class DTOParametersMatchValidator implements ConstraintValidator<DTOParametersMatch, Object[]> {
private static final String ILLEGAL_ARGS_BASE =
"Illegal usage of DTOParametersMatch; "
+ "requires two parameters where first is a UUID and second is a DTO.";
#Override
public boolean isValid(Object[] values, ConstraintValidatorContext context) {
System.out.println("***** DTOParametersMatchValidator isValid called");
var pathId = (UUID)v0;
var body = (DTO)v1;
return pathId.equals(body.getId());
}
}
I'm not sure if I missed something or set it up wrong..
One of my colleagues pointed out I was missing the #Validated annotation on the controller class. Once I added that, everything worked.
I am trying to validate my rest request according to some fields existance. For example, if transactionDate field is null or didnt exist in my request object, I want to throw an error to client.
I couldn't do it despite the source of this guide and still my requests can pass in controller.
How can I validate two or more fields in combination?
DTO
#FraudRestRequestValidator
public class FraudActionsRestRequest {
private BigDecimal amount;
private String receiverTransactionDate;
private String receiverNameSurname;
private BigDecimal exchangeRate;
private String transactionReferenceNumber;
#NotNull
private String transactionDate;
#NotNull
private String transactionTime;
private String transactionTimeMilliseconds;
private BigDecimal tlAmount;
private String channel;
}
ANNOTATION
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface FraudRestRequestValidator {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
VALIDATOR
public class FraudActionsRestValidator implements ConstraintValidator<FraudRestRequestValidator, FraudActionsRestRequest> {
#Override
public void initialize(FraudRestRequestValidator constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
//I will implement my logic in future
return false;
}
}
REST CONTROLLER
#PostMapping("/getFraudActions")
public ResponseEntity<?> getFraudActions(#Valid #RequestBody FraudActionsRestRequest fraudActionsRestRequest, Errors errors) throws Exception
Thanks.
In your custom validator just implement logic you want to have. You did everything correct except some minor thing:
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidFraudRestRequest {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class FraudActionsRestValidator implements ConstraintValidator<ValidFraudRestRequest, FraudActionsRestRequest> {
#Override
public void initialize(ValidFraudRestRequest constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
return fraudActionsRestRequest.getTransactionDate() != null && fraudActionsRestRequest.getTransactionTime() != null && additional check you need;
}
}
Looks all okaish.
You might be missing the #Validated annotation on the rest controller class,
See https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-validation.html for more info
I want to Implement a validation in a jersey such that if I send a duplicate value of UserName or Email which already exists in DataBase then it should throw an Error saying UserName/Email already exists.
How can I acheive this?
I gone through this jersey documentation
https://jersey.java.net/documentation/latest/bean-validation.html
https://github.com/jersey/jersey/tree/2.6/examples/bean-validation-webapp/src
But I couldn't understood what exactly I have to follow to make my custom Jersey validations.
Suppose I send a Json in Body while Creating a User like:
{
"name":"Krdd",
"userName":"khnfknf",
"password":"sfastet",
"email":"xyz#gmail.com",
"createdBy":"xyz",
"modifiedBy":"xyz",
"createdAt":"",
"modifiedAt":"",
}
Thanks in Advance for your helping hands.
Assuming you have a request instance of class:
public class UserRequest {
// --> NOTICE THE ANNOTATION HERE <--
#UniqueEmail(message = "email already registered")
private final String email;
public UserRequest(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
You have to add a new annotation (and link it to your validator class using #Constraint):
#Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = { UniqueEmailValidator.class })
#Documented
public #interface UniqueEmail {
String message();
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
then you also have to implement the validation itself:
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, UserRequest> {
#Override
public void initialize(UniqueEmail constraintAnnotation) {
}
#Override
public boolean isValid(UserRequest value, ConstraintValidatorContext context) {
// call to the DB and verify that value.getEmail() is unique
return false;
}
}
and you're done. Remember that Jersey is using HK2 internally so binding some sort of a DAO to your Validator instance can be tricky if you use Spring or other DI.
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.