Spring request validation - spring

I discover that Spring request validation differs from laravel-validation.
I must validate fields by type and by other constraints (e.g. max length). But Spring throws different exceptions when field cannot be casted to Dto’s field type (thrown by Jackson) or it’s just too long (thrown by jsr-303).
How can I validate JSON request by JSON-schema? I think it’s better solution.

Use javax.validation.constraints on your DTOs. Here is an example from baeldung
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
public class User {
#NotNull(message = "Name cannot be null")
private String name;
#AssertTrue
private boolean working;
#Size(min = 10, max = 200, message
= "About Me must be between 10 and 200 characters")
private String aboutMe;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int age;
#Email(message = "Email should be valid")
private String email;
// standard setters and getters
}

Okay, I deal with the problem using java-json-tools/JSON-Schema-Validator library.
I have created service with only one method: “validate” which receives JsonNode and name of file with json-schema. If validation fails, validator throws my custom exception, and exception handler renders error message

Related

RequestBody and camel case request parameters

I have an endpoint with a request body:
#PostMapping("/v1/message")
#PreAuthorize("#oauth2.isAnyClientId(#authorizeConfig.getIds())")
public void receive(
#RequestBody #NotNull #Valid final Message message,
#RequestHeader final HttpHeaders headers) {
...
}
The Message class has several parameters:
#Builder
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor
#JsonPropertyOrder(alphabetic = true)
public class Message {
#NotBlank(message = "tenant is empty")
private String tenant;
#NotBlank(message = "messageId is empty")
private String messageId;
#NotBlank(message = "payload is empty")
private String payload;
#NotNull(message = "messageType is empty")
private MessageType messageType;
private String xSchemaVersion;
}
When I perform a request to that endpoint:
{
"tenant":"client1",
"messageId":"670f13e15d554b2bba56f6d76d33b79c",
"payload":"",
"messageType":"MessageType.Email",
"xSchemaVersion":"2.7.3"
}
I get the following error:
{
"status": 400,
"date": "2022-07-18T08:21:21.430+0000",
"exception": "missing parameter: xSchemaVersion",
"description": "uri=/v1/message"
}
But if I do the following instead:
{
"tenant":"client1",
"messageId":"670f13e15d554b2bba56f6d76d33b79c",
"payload":"",
"messageType":"MessageType.Email",
"xschemaVersion":"2.7.3"
}
I get 200 OK.
Notice that the only thing I changed is the lowercase s in xschemaVersion instead of xSchemaVersion.
Why is this necessary?
This is happening because you are using lomboks getter & setter for your Message entity.
If you check, lombok will generate get & set for your xSchemaVersion as
getXSchemaVersion() & setXSchemaVersion(..), which is quite different than what any IDE would automatically generate for us.
If I use eclipse for generating getters & setters for xSchemaVersion it would be getxSchemaVersion() & setxSchemaVersion(..); and this is what spring is also looking for as valid getter & setter for field xSchemaVersion (based on reflaction).
Because lombok is making your x & s as an upper case, spring is unable to map your xSchemaVersion with input if you use xSchemaVersion.
The reason it works with xschemaVersion because, spring is able to map field as it does lowecase conversion for words after get & set( getXSchemaVersion() & setXSchemaVersion(..).)
Workaround -- create separate getter & setter in your Message for xSchemaVersion as getxSchemaVersion & setxSchemaVersion(..) which acts as overriding methods of default generated getter /setter of lombok.
Once you do this, you can successfully able to use xSchemaVersoin in your input.
According to Project Lombok https://projectlombok.org/features/GetterSetter
you can add AccessLevel.PRIVATE to the GETTER SETTER for not used field.
#Getter(AccessLevel.PRIVATE) #Setter(AccessLevel.PRIVATE) String xSchemaVersion;

Could not extract response: no suitable HttpMessageConverter found for response type and content type [binary/octet-stream]

So i am consuming JSON response from this URL through RestTemplate
Link:
"https://s3-ap-southeast-1.amazonaws.com/he-public-data/productdf38641.json"
My Product POJO:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ProductModel {
private String uniq_id;
private String product_name;
private int retail_price;
private int discounted_price;
private List<String> image;
private String description;
private String product_rating;
private String overall_rating;
private String brand;
}
Now when i use restTemplate to store this Array of Json object in ProductModel[].
ProductModel[] books = restTemplate.getForObject(URL, ProductModel[].class);
I am getting this error
Caused by: org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.schema.testing.domain.ProductModel] and content type [binary/octet-stream]
when i pass the same JSON object though postman to REST endpoint via POST request.
it is able to process that request.
IS this all game related to content-type.
Please help , what do i have to do next. i am not sure .
Any help is appreciated
I guess there is a solution for this exception. Try out and let me know the result.
Not any message converter can read your HTTP response, so it fails with an exception.
The main problem here is a content-type, In order to overcome this, you can introduce a custom message converter. and register it for all kinds of responses (i.e. ignore the response content-type header). Just like this
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// We are making this converter to process any kind of response, not only application/*json, which is the default behaviour
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);

#Valid for long data type is not working for mandatory check

I have the below input class and when i trigger the api without 'interactionId' param in the input,
I expect validation error message "interactionId cannot be empty" but the validation passes through which i guess could be due to the fact that interactionId has a default value of 0.
Can someone pls. help to enforce this validation on the 'long' parameter when its not given in input?
with #NotEmpty for the customerId param, its working as expected. Using #NotEmpty for the long param "interactionId" is throwing a different error that #notempty cannot be used for long.
public class Input {
#NotEmpty(message = "customerId cannot be empty")
private String customerId;
#Valid
#NotNull(message = "interactionId cannot be empty")
private long interactionId;
// setters and getters
}
my controller class:
#RestController
public class Controller {
#PostMapping(value="/detailed-customer-transaction", produces =
MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Object> detailTransactions(#Valid #RequestBody Input
params)
{
return new ResponseEntity<>(Dao.detailTransactions(params), HttpStatus.OK);
}
Above issues is resolved after changing to Long instead of long.
Query #2
I need another help. I have a String input param which takes date-time format in below format. Given its a string parameter, how can i validate for the pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
long should be Long, because long is a primary type in java, not an object, so Long is an object that can be checked whether it is null

Spring Boot : How to do REST validation based on input group?

I have a form in which :
firstname and lastname are mandatory fields for registered user.
ssn for new user.
contract number for owner.
So, on clicking the submit button, REST API (connect API) is called with values
from either of the above groups.
My bean class has members :
FN
LN
SSN
contractNum
How do I validate using bean/hibernate validator and identify which group has been passed ?
From the Hibernate Documentation, you can read for detail
https://hibernate.org/validator/
Hibernate Validator allows to express and validate application
constraints. The default metadata source are annotations, with the
ability to override and extend through the use of XML. It is not tied
to a specific application tier or programming model and is available
for both server and client application programming. But a simple
example says more than 1000 words:
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class User {
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
private String ssn;
}
Bean Validation is best used for simple validation logic. If your validation requires more complexity, use Spring's Validator interface instead.
I don't know the context domain, so I'll just call your bean "Form" with all String fields for the example:
public class Form {
private String firstName;
private String lastName;
private String ssn;
private String contractNumber;
// getters and setters
}
Then create a validator for this class:
public class FormValidator implements Validator {
public boolean supports(Class clazz) {
return Form.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
Form form = (Form) target;
// validation logic
}
}
Then you can simply use it like this:
Form form = ...;
Validator validator = new FormValidator();
Errors errors = new Errors();
validator.validate(form, errors);
if (errors.hasErrors() {
// not valid
} else {
// is valid
}

Doing JSR-303 validation in logical order

I have such field in my domain model class validation constraints:
#Column(nullable = false, name = "name")
#NotEmpty(groups = {Envelope.Insert.class, Envelope.Update.class})
#Size(min = 3, max = 32)
private String name;
When this field is empty ("") or null, validator produces both "cannot be empty" and "size must be between..." error messages. I understand it, but when I show this validation error to the client, it seems quite odd (because when something is null / empty it cannot fulfill size requirement, it's not a logical).
Is there some way how to tell Spring to do validation in proper order? If is not #NotEmpty then do not check #Size, and when #NotEmpty is fulfilled check #Size.
According to Hibernate official document:
By default, constraints are evaluated in no particular order and this
regardless of which groups they belong to. In some situations,
however, it is useful to control the order of the constraints
evaluation. In order to implement such an order one would define a new
interface and annotate it with #GroupSequence defining the order in
which the groups have to be validated.
At first, create two interface FirstOrder.class and SecondOrder.class and then define a group sequence inside OrderedChecks.java using #GroupSequence annotation.
public interface FirstOrder {
}
public interface SecondOrder {
}
#GroupSequence({FirstOrder.class, SecondOrder.class})
public interface OrderedChecks {
}
Finally, add groups in your bean constraints annotations.
#Column(nullable = false, name = "name")
#NotEmpty(groups = {FirstOrder.class, Envelope.Insert.class, Envelope.Update.class})
#Size(min = 3, max = 32, groups=SecondOrder.class)
private String name;
The following example is taken from the JSR-303 docs
public class Address {
#NotEmpty(groups = Minimal.class)
#Size(max=50, groups=FirstStep.class)
private String street1;
#NotEmpty(groups=SecondStep.class)
private String city;
#NotEmpty(groups = {Minimal.class, SecondStep.class})
private String zipCode;
...
public interface FirstStep {}
public interface SecondStep {}
#GroupSequence({Firststep.class, SecondStep.class})
public interface Total {}
}
and calling the validator like this
validator.validate(address, Minimal.class, Total.class);

Resources