According to the current doc (5.0.0.RELEASE) Spring Webflux supports validation when working with annotated controllers:
By default if Bean Validation is present on the classpath — e.g.
Hibernate Validator, the LocalValidatorFactoryBean is registered as a
global Validator for use with #Valid and Validated on #Controller
method arguments.
However nothing is said about how to automate it with functional endpoints. In fact, the only example of input processing in the documentation doesn't validate anything:
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}
Are we supposed to do this manually or there is some automatic way to do it?
In Spring version 5.0, there is no automatic way to do validation in functional endpoints, and as such validation must be done manually.
Though there are currently no concrete plans to do so, we might add some sort of validation in the future. But even then it will be an explicit method call, and not an automatic mechanism. Overall, the functional endpoint model is designed to be a lot more explicit than the annotation-based model.
As arjen-poutsma said, it seems there is no way of running automated validations on Spring 5 functional endpoints.
Spring documentation is not very clear about this, and it doesn't suggest any approach.
On this Baeldung article, you'll find an idea on how you can run validations using this approach (disclaimer: I'm the writer of the article :) )
In a nutshell, you can follow these steps:
Implement Spring Validators to evaluate your resources
Create an abstract class with the basic procedure that any handler will follow when processing a request, leaving up to the children classes what to do when the data is valid
Make your request handler classes extend this abstract class, implementing this abstract method, stating the body it will be expecting, and what validator needs to be used to validate it
EDIT:
I've been following this related Spring issue, and it seems we now count with official documentation regarding this subject: https://github.com/spring-projects/spring-framework/blob/master/src/docs/asciidoc/web/webflux-functional.adoc#validation
The suggested approach is to use validators as explained in the article.
At the current version(2.0.4.RELEASE) there isn't a way to do automatic validation with handles, however you always could make a manual validation like this:
#Slf4j
#Component
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
#RequiredArgsConstructor
public class MyHandlerValidator implements HandlerValidator<MyResource> {
Validator validator;
#Override
public void callValidator(final MyResource fdr) {
final DataBinder binder = new DataBinder(fdr);
binder.setValidator(validator);
binder.validate();
if (binder.getBindingResult().hasErrors()) {
final String reason = binder.getBindingResult().getFieldError().toString();
log.error(reason);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason);
}
}
}
The thing with this, its that the you should throw a WebExchangeBindException like automatic validation does, however i could't create a MethodParameter witch is a dependency to create this exception.
UPDATE:
Spring show us a way to do it, which is similar to my solution, but, not enough in my opinion on documentation
Just to demo some working code. If you need simple validation based on the object annotations like:
#Value
#Builder
#Jacksonized
public class SigninRequest {
#NotBlank(message = "The username is mandatory")
#Email(message = "The username should be valid Email")
String username;
#NotBlank(message = "The password is mandatory")
String password;
}
At the handler you need just one simple additional operator doOnNext:
#Component
#RequiredArgsConstructor
public class AuthHandler {
private final AuthService authService;
private final ObjectValidator validator;
public Mono<ServerResponse> signin(ServerRequest request) {
return ok().body(
request.bodyToMono(SigninRequest.class)
.doOnNext(validator::validate) //<-- just one single line
.flatMap(login -> authService.authenticate(login.getUsername(), login.getPassword())),
AuthResult.class);
}
}
The ObjectValidator is doing actual validation and throws the runtime exception with the 4xx error in case of validation errors:
#Component
#RequiredArgsConstructor
public class ObjectValidator {
private final Validator validator;
public <T> T validate(T object) {
var errors = validator.validate(object);
if (errors.isEmpty()) {
return object;
} else {
String errorDetails = errors.stream().map(er -> er.getMessage()).collect(Collectors.joining(", "));
throw new ObjectValidationException(errorDetails);
}
}
}
And the exception:
#ResponseStatus(code = HttpStatus.UNPROCESSABLE_ENTITY)
public class ObjectValidationException extends RuntimeException {
public ObjectValidationException(String errorDetails) {
super("Please supply the valid data: " + errorDetails);
}
}
If you properly setup global error handling you can keep you handler code clean and reuse the object validator across all your handlers.
Related
How can i write Mockmvc test case for below code:
My controller class
#RestController
public class CartController {
#Autowired
private CartService cartService;
#GetMapping(path = "/addToCart", produces = MediaType.APPLICATION_JSON_VALUE)
public String cart(#Valid #RequestBody Cart cart) {
return cartService.cart(cart);
}
}
My CartService class:
#Service
public class CartService {
private LoginRepository loginRepository;
#Autowired
private ProductRepository productRepository;
#Autowired
private CartRepository cartRepository;
#Autowired
private EmailService emailService;
public CartService(LoginRepository loginRepository) {
this.loginRepository = loginRepository;
}
public String cart(Cart cart) {
String username = cart.getUserName();
System.out.println(username);
String password = cart.getPassword();
String email = cart.getEmail();
if (loginRepository.existsByUserNameAndPassword(username, password)) {
String productname = cart.getProductName();
System.out.println(productname);
String price = cart.getPrice();
String discription = cart.getDiscription();
if (productname != null) {
if (productRepository.existsByProductNameAndPriceAndDiscription(productname, price, discription)) {
Integer count = cartRepository.countByUserName(username);
System.out.println(count);
cartRepository.save(new Cart(username, password, email, productname, price, discription, count));
return "{\"message\":\"product Successfully added to cart\"}";
} else {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "entity not found"
);
}
} else {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
} else {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
}
I don't know how to write test case for above code using mockmvc. i don't know how can i write mockmvc test case for if else statement. so please help me how to write mockmvc test case for entire code so that i can do mockmvc test for if else statment also.
I think you have a misunderstanding of what #WebMvcTest is used for.
It is for Integration Testing a single slice of your Application – namely Web MVC.
This means that your #WebMvcTest annotated Test should only use Mocks of your Service because what they should test is all the Web related stuff (proper conversion to JSON, XML; returning correct response codes, etc)
There is a tutorial on spring.io which should answer your questions.
For Testing your Service you can use plain old Unit Test with for example JUnit.
In order for that to work you need to do some rework on your classes. First thing I would suggest is to replace the field injections with constructor injection. This is the recommended way of injection. Read here for more information.
After this change you can mock the dependencies of your Service (for example with Mockito) and pass them via the constructor.
This way you can test the different if/else branches in your code.
And last but not least I would highly recommend to do some other rework on your service. Currently it returns information that are highly coupled to the Web context(the manually crafted JSON, the ResponseStatusException). The handling of those is the responsibility of your controller. Your Service should be independent of the thing (the controller in your case) that uses it. Just imagine your Service will be used by a other Class for a CLI Tool which does know nothing about JSON and response statuses.
Is there a way to add validation to feign clients on the request parameters.
For example:
#FeignClient
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") String zipCode);
}
It would be nice to verify that zipcode is not empty and is of certain length etc, before sending the HTTP call to the server.
If your validations are simple, apply to only headers and query string parameters, you can use a RequestInterceptor for this, as it provides you the opportunity to review the RequestTemplate before it is sent to the Client.
public class ValidatingRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
// use the methods on the request template to check the query and values.
// throw an exception if the request is not valid.
}
}
If you need to validate the request body, you can use a custom Encoder
public class ValidatingEncoder implements Encoder {
public void encode(Object object, Type type, RequestTemplate template) {
// validate the object
// throw an exception if the request is not valid.
}
}
Lastly, if you want to validate individual parameters, you can provide a custom Expander for the parameter and validate it there. You can look at this answer for a complete explanation on how to create a custom expander that can work with Spring Cloud.
How to custom #FeignClient Expander to convert param?
For completeness, I've included an example for how to do this with vanilla Feign.
public class ZipCodeExpander implements Expander {
public String expand(Object value) {
// validate the object
// throw an exception if the request is not valid.
}
}
public interface ZipCodeClient {
#RequestLine("GET /zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#Param(expander = ZipCodeExpander.class) ("zipCode") String zipCode);
}
As pointed out in this comment, a solution using the Bean Validation API would be nice. And indeed, I found in a Spring Boot project that merely placing #org.springframework.validation.annotation.Validated on the interface is sufficient for enabling Bean Validation.
So for example:
#FeignClient
#Validated
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") #NotEmpty String zipCode);
}
triggering a ConstraintViolationException in the case of violations.
Any standard Bean Validation feature should work here.
UDPATE Note that there seems to be a potential issue with this solution that might require setting a Hibernate Validator configuration property like this: hibernate.validator.allow_parallel_method_parameter_constraint=true
I'm writing a rest service using spring boot with Jersey and MongoDB starter packages. So I have validation working on top level documents by creating the beans:
#Configuration
public class MongoValidationBeans {
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
I have a document:
#Document
public class SomeDocument {
#NotEmpty(message="error message that shows on console")
private Set<NonDocumentObject> referencesToOtherDocuments;
}
With set of embedded objects:
public class NonDocumentObject {
#NotNull(message="can't see this error message")
private ObjectId referenceId;
#NotBlank
private String referenceInfo;
}
The validation beans respect the #NotEmpty annotation on my set of objects, but they do not respect #NotNull or #NotBlank annotations on fields on my NonDocumentObject. How can I get validation to work on the fields of my embedded Set of objects.
EDIT: #Valid fixes the above problem.
Also, when a constraint violation happens on my top level document, I can see the specific message on my console but tomcat returns an http error page with response status 400. How can I instead send a json object with more information about the error? I have a class
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {}
which catches 404, 405, etc exceptions and returns a json object with the appropriate information, but does not catch mongo constraint validations. I think I need to throw exceptions from the mongo validation beans but can't find resources that direct me how to.
I also want to be able to embed other objects into NonDocumentObject with its own validation. Would it be possible?
So the #Valid annotation triggers cascade validation, but I still can't figure out how to catch validation errors with an exception mapper, or some other way to catch validation errors.
I would like to configure and use a Spring 4.1 AsyncUncaughtExceptionHandler. According to the Spring team (see relevant comment here) one will be able to configure an AsyncUncaughtExceptionHandler either by with the <task:annotation-driven> or by implementing AsyncConfigurer as shown here:
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler() ;
}
Now my question is as follows: Is there another web-layer annotation similar to #ExceptionHandler that would work like a AsyncUncaughtExceptionHandler?
As stated in the comment, here's an approach I've taken:
It's about async data imports so all classes are called Import...
What I did not do (yet) is the uncaught exception handling, but reading your post made me think about it and it should be straight forward with Spring-AOP wrapping the Importer.process() methods. This will not be global solution but it would be adaptable for a complete application by using a more generalized Result object.
The Controller uses the ImportRequests to get processing (or done) messages. The Importer itself is not removing the results from the map but this is delegated to the controller instead (A user is clicking delete). We also have a #Scheduled task which cleans up done results after 1 hour to ensure there are not left-overs.
So here's part of the code that the Controller is able to get import results during processing:
#Service
public class ImportRequests {
private final Map<User, ImportResult> importRequests = new ConcurrentHashMap<>();
/** Add, remove, get methods for current user omitted */
}
public class ImportResult {
/** The done. */
private Future<Boolean> done;
/** The error messages. */
private List<String> messages = Collections.synchronizedList(new ArrayList<String>());;
}
#Service
public class ImportService {
#Autowired
private ImportRequests importRequests;
#Autowired
private Importer importer;
public ImportResult doImport(final ImportForm importForm) {
ImportResult result = new ImportResult();
importRequests.addImportResultForCurrentUser(result);
/* This is the actual Async call (process) */
result.setDone(importer.process(result));
return result;
}
}
#Service
public class ImporterImpl implements Importer {
/**
* doProcess will import the *big* file and update the result object with the necessary messages
*/
#Async
public Future<Boolean> process(ImportResult result) {
Boolean done = doProcess(result);
return new AsyncResult<Boolean>(done);
}
}
Hope this helps.
Original Text:
One possibility that I have used is the "#ControllerAdvice" on a class scanned by the servletcontext.
You simply create a method with the exception as a parameter and annotate that method with "#ExceptionHandler". You can even have multiple handlers for specific exception types.
The result of these methods are again handled by the DispatcherServlet, so you can render a view the same way as with request mappings.
I want to use validation not with web controllers. Suppose I have a class Person
public class Person {
private String name;
private String surname;
//getters and setters
...
}
Also I have a validator class:
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname",
"surname.empty");
}
}
How I can use this validator for example in console application? Or validation is only for web application in spring?
You can use the validation tools in a console application. You simply need to call ValidationUtils.invokeValidator(validator, object, errors). Your main concern would be having a suitable Errors instance. You would probably end up using BeanPropertyBindingResult, or subclassing AbstractErrors.
You probably know, but you should consult the Spring reference and javadoc.
Rough guess at untested code:
Person person = new Person();
Errors errors = new BeanPropertyBindingResult(person, "person");
ValidationUtuls.invokeValidator(new PersonValidator(), person, errors);
if (errors.hasErrors()) { ... }
Out of interest, why are you using Spring validation in preference to javax.validation? I've found that it's generally easier to use the javax.validaton/JSR-303 API. Hibernate Validator is the reference implementation and Spring integrates with JSR-303.