Course – LS (cat=REST)

Get started with Spring and Spring Boot, through the reference Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

When we want to generate validations with Swagger, we generally use the basic specifications. However, we might need to add Spring custom validation annotations.

This tutorial will teach how to generate models and REST APIs using these validations while focusing on the OpenAPI server generator and not the constraint validators.

2. Setup

For the setup, we’ll use a previous Baeldung tutorial to generate a server from an OpenAPI 3.0.0 definition. Next, we’re going to add some custom validation annotations alongside all needed dependencies.

3. PetStore API OpenAPI Definition

Let’s suppose we have the PetStore API OpenAPI definition, and we need to add custom validations for both the REST API and the described model, Pet.

3.1. Custom Validations for the API Model

To create pets, we need to make Swagger use our custom validation annotation to test if the pet’s name is capitalized. As a result, for the sake of this tutorial, we’ll just call it Capitalized.

Thus, observe the x-constraints specification in the below example. It’ll be enough to let Swagger know we need to generate another type of annotation than the already known ones:

openapi: 3.0.1
info:
  version: "1.0"
  title: PetStore
paths:
  /pets:
    post:
      #.. post described here
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
          x-constraints: "Capitalized(required = true)"
        tag:
          type: string

3.2. Custom Validations for the REST API Endpoint

As above, we’ll describe an endpoint to find all pets by name in the same manner. To demonstrate our purpose, let’s suppose our system is case sensitive so that we’ll add the same x-constraints validation again for the name input parameter:

/pets:
    # post defined here
    get: 
      tags: 
        - pet 
      summary: Finds Pets by name 
      description: 'Find pets by name' 
      operationId: findPetsByTags 
      parameters: 
        - name: name
          in: query 
          schema:
            type: string 
          description: Tags to filter by 
          required: true 
          x-constraints: "Capitalized(required = true)" 
      responses: 
        '200':
          description: default response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        '400': 
          description: Invalid tag value

4. Creating the Capitalized Annotation

To enforce a custom validation, we need to create an annotation assuring the functionality.

First, we make the annotation interface – @Capitalized:

@Documented
@Constraint(validatedBy = {Capitalized.class})
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Capitalized{
    String message() default "Name should be capitalized.";
    boolean required() default true;
    // default annotation methods
}

Note that we made the required method for demo purposes – we’ll explain that later.

Next, we add the CapitalizedValidator, referred to in the above @Constraint annotation:

public class CapitalizedValidator implements ConstraintValidator<Capitalized, String> {

    @Override
    public boolean isValid(String nameField, ConstraintValidatorContext context) {
        // validation code here
    }
}

5. Generating the Validation Annotations

5.1. Specifying the Mustache Templates Directory

To generate models with the @Capitalized validation annotation, we need specific mustache templates telling Swagger to generate it within the model.

Thus, in the OpenAPI generator plugin, inside the <configuration>[..]</configuration tags, we need to add a template directory:

<plugin>  
  //... 
  <executions>
    <execution>
      <configuration
        //...
        <templateDirectory>
          ${project.basedir}/src/main/resources/openapi/templates
        </templateDirectory>
        //...
      </configuration>
    </execution>
  </executions>        
  //...
</plugin> 

5.2. Adding the Mustache Bean Validations Configurations

In this chapter, we’ll configure Mustache templates to generate the validation specification. To add more details, we will modify the beanValidationCore.mustache, the model.mustache, and the api.muctache files to generate code successfully.

Firstly, the beanValidationCore.mustache from the swagger-codegen  module needs to be modified by adding a vendor extension specification:

{{{ vendorExtensions.x-constraints }}}

Secondly, if we have an annotation with an inner property like @Capitalized(required = “true”), then a particular pattern on the second line of the beanValidationCore.mustache file needs to be specified:

{{#required}}@Capitalized(required="{{{pattern}}}") {{/required}}

Thirdly, we need to change the model.mustache specification to contain the necessary imports. For example, we’ll import the @Capitalized annotation and the Capitalized. The imports should be inserted after the package tag of the model.mustache:

{{#imports}}import {{import}}; {{/imports}} import 
com.baeldung.openapi.petstore.validator.CapitalizedValidator; 
import com.baeldung.openapi.petstore.validator.Capitalized;

Lastly, to have the annotation generation inside the APIs, we need to add the imports for the @Capitalized annotations in the api.mustache file.

{{#imports}}import {{import}}; {{/imports}} import 
com.baeldung.openapi.petstore.validator.Capitalized;

Additionally, api.mustache depends on the cookieParams.mustache file. Thus, we need to add it in the openapi/templates directory.

6. Generate Sources

To finish, we can use the generated code. We need to run, at a minimum, mvn generate-sources. This will generate the model:

public class Pet {
    @JsonProperty("id")
    private Long id = null;

    @JsonProperty("name")
    private String name = null;
    // other parameters
    @Schema(required = true, description = "")
    @Capitalized public String getName() { return name; } // default getters and setter }

And it’ll also generate an API:

default ResponseEntity<List<Pet>> findPetsByTags(
    @Capitalized(required = true)
    @ApiParam(value = "Tags to filter by") 
    @Valid @RequestParam(value = "name", required = false) String name) {
    
    // default generated code here 
    return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}

7. Testing Using curl

After starting up the application, we’ll run some curl commands to test it.

Additionally, note that constraint violations do throw a ConstraintViolationException. The exception needs to be appropriately handled via @ControllerAdvice to return a 400 Bad Request status.

7.1. Testing the Pet Model Validation

This Pet model has a lowercase name. Thus, a 400 Bad Request should be returned by the app:

curl -X 'POST' \
  'http://localhost:8080/pet' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": 1,
  "name": "rockie"
}'

7.2. Testing the Find Pet API

In the same manner as above, because the name is lowercase, the application should also return a 400 Bad Request:

curl -I http://localhost:8080/pets/name="rockie"

8. Conclusion

In this tutorial, we’ve seen how to enable custom constraint validators to be generated with Spring while implementing a REST API server.

As always, the code can be found over on GitHub.

Course – LS (cat=REST)

Get started with Spring and Spring Boot, through the Learn Spring course :

>> CHECK OUT THE COURSE
res – REST (eBook) (cat=REST)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.