Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data Rest Validation Confusion

Looking for some help with Spring data rest validation regarding proper handling of validation errors:

I'm so confused with the docs regarding spring-data-rest validation here: http://docs.spring.io/spring-data/rest/docs/current/reference/html/#validation

I am trying to properly deal with validation for a POST call that tries to save a new Company entity

I got this entity:

@Entity
public class Company implements Serializable {

@Id
@GeneratedValue
private Long id;

@NotNull
private String name;

private String address;

private String city;

private String country;

private String email;

private String phoneNumber;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
private Set<Owner> owners = new HashSet<>();

public Company() {
    super();
}

...

and this RestResource dao

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RestResource;

import com.domain.Company;

@RestResource
public interface CompanyDao extends PagingAndSortingRepository<Company,   Long> {


}

POST Request to api/Companies:

{
  "address" : "One Microsoft Way",
  "city" : "Redmond",
  "country" : "USA",
  "email" : "[email protected]",
  "phoneNumber" : "(425) 703-6214"

}

When I issue a POST with a null name , I get the following rest response with httpcode 500

{"timestamp":1455131008472,"status":500,"error":"Internal Server Error","exception":"javax.validation.ConstraintViolationException","message":"Validation failed for classes [com.domain.Company] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=name, rootBeanClass=class com.domain.Company, messageTemplate='{javax.validation.constraints.NotNull.message}'}\n]","path":"/api/companies/"}

I tried creating the following bean, but it never seems to do anything:

@Component(value="beforeCreateCompanyValidator")
public class BeforeCreateCompanyValidator implements Validator{

@Override
public boolean supports(Class<?> clazz) {
    return Company.class.isAssignableFrom(clazz);
}

@Override
public void validate(Object arg0, Errors arg1) {
    System.out.println("xxxxxxxx");


}

}

and even if it did work, how would it help me in developing a better error response with a proper http code and understandable json response ?

so confused

using 1.3.2.RELEASE

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
like image 705
1977 Avatar asked Feb 10 '16 19:02

1977


People also ask

Is @valid and @validated the same?

The @Valid annotation ensures the validation of the whole object. Importantly, it performs the validation of the whole object graph. However, this creates issues for scenarios needing only partial validation. On the other hand, we can use @Validated for group validation, including the above partial validation.

What does @validated do?

The @Validated annotation is a class-level annotation that we can use to tell Spring to validate parameters that are passed into a method of the annotated class. We'll learn more about how to use it in the section about validating path variables and request parameters.

What does @valid do in Spring boot?

When Spring Boot finds an argument annotated with @Valid, it automatically bootstraps the default JSR 380 implementation — Hibernate Validator — and validates the argument. When the target argument fails to pass the validation, Spring Boot throws a MethodArgumentNotValidException exception.

What is @valid in Spring MVC?

The Spring MVC Validation is used to restrict the input provided by the user. To validate the user's input, the Spring 4 or higher version supports and use Bean Validation API. It can validate both server-side as well as client-side applications.


2 Answers


@Mathias it seems the following is enough for jsr 303 annotations to be checked and for it to auto return a http code of 400 with nice messages (I dont even need BeforeCreateCompanyValidator or BeforeSaveCompanyValidator classes):

@Configuration
public class RestValidationConfiguration extends RepositoryRestConfigurerAdapter{

@Bean
@Primary
/**
 * Create a validator to use in bean validation - primary to be able to autowire without qualifier
 */
Validator validator() {
    return new LocalValidatorFactoryBean();
}


@Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
    Validator validator = validator();
    //bean validation always before save and create
    validatingListener.addValidator("beforeCreate", validator);
    validatingListener.addValidator("beforeSave", validator);
}

}

400 response:

{
    "errors": [{
        "entity": "Company",
        "message": "may not be null",
        "invalidValue": "null",
        "property": "name"
    }, {
        "entity": "Company",
        "message": "may not be null",
        "invalidValue": "null",
        "property": "address"
    }]
}
like image 105
1977 Avatar answered Sep 20 '22 10:09

1977


I think your problem is that the bean validation is happening too late - it is done on the JPA level before persist. I found that - unlike spring mvc - spring-data-rest is not doing bean validation when a controller method is invoked. You will need some extra configuration for this.

You want spring-data-rest to validate your bean - this will give you nice error messages responses and a proper http return code.

I configured my validation in spring-data-rest like this:

@Configuration
public class MySpringDataRestValidationConfiguration extends RepositoryRestConfigurerAdapter {

    @Bean
    @Primary
    /**
     * Create a validator to use in bean validation - primary to be able to autowire without qualifier
     */
    Validator validator() {
        return new LocalValidatorFactoryBean();
    }

    @Bean
    //the bean name starting with beforeCreate will result into registering the validator before insert
    public BeforeCreateCompanyValidator beforeCreateCompanyValidator() {
        return new BeforeCreateCompanyValidator();
    }

    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        Validator validator = validator();
        //bean validation always before save and create
        validatingListener.addValidator("beforeCreate", validator);
        validatingListener.addValidator("beforeSave", validator);
    }
}

When bean validation and/or my custom validator find errors I receive a 400 - bad request with a payload like this:

    Status = 400
    Error message = null
    Headers = {Content-Type=[application/hal+json]}
    Content type = application/hal+json
   Body = {
     "errors" : [ {
     "entity" : "siteWithAdminUser",
     "message" : "may not be null",
     "invalidValue" : "null",
     "property" : "adminUser"
     } ]
   }
like image 29
Mathias Dpunkt Avatar answered Sep 23 '22 10:09

Mathias Dpunkt