DEV Community

Cover image for annotation mapping & bean-validation
Martin Hauner
Martin Hauner

Posted on • Updated on • Originally published at openapiprocessor.io

annotation mapping & bean-validation

The openapi-processor-spring/micronaut release 2022.5 adds a new annotation type mapping feature. It provides the possibility to add additional annotations to generated interfaces & classes.

what is openapi-processor?

openapi-processor is a small framework to process OpenAPI yaml files. Currently, openapi-processor provides java code generation for Spring Boot & Micronaut.

It does support gradle and maven with plugins to convert OpenAPI yaml files to java (controller) interfaces & (payload) pojo classes as part of the build process.

It generates Java because the controller interface and pojo classes are easily usable from other jvm languages. Usually you will just use them without looking at the generated code.

annotation mapping

annotation type mapping allows to add annotations to a generated model class or to an endpoint method parameters of that class.

Let's look at a contrived example to add a custom bean validation to the pojo model class of a schema.

See annotation mapping documentation for more.

the example api

here is a simple api that takes a Foo schema as request body. The schema has some (useless ;-) number constraints on its properties.

openapi.yaml

openapi: 3.1.0
info:
  title: annotation mapping example
  version: 1.0.0

paths:
  /foo:
    post:
      summary: annotation mapping example endpoint.
      description: a simple endpoint where an annotation mapping is used on the request body
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Foo'
        required: true
      responses:
        '201':
          description: empty response

components:
  schemas:
    Foo:
      type: object
      properties:
        foo1:
          type: integer
          minimum: 0
        foo2:
          type: integer
          minimum: -10
Enter fullscreen mode Exit fullscreen mode

bean validation annotations

Enabling bean validation in the mapping.yaml (the processor configuration) will generate a Foo class with bean validation annotations for the property constraints.

generated file

package io.openapiprocessor.openapi.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;
import javax.validation.constraints.DecimalMin;

@Generated(value = "openapi-processor-spring", version = "2022.5")
public class Foo {

    @DecimalMin(value = "0") // <1>
    @JsonProperty("foo1")
    private Integer foo1;

    @DecimalMin(value = "-10") // <1>
    @JsonProperty("foo2")
    private Integer foo2;

    public Integer getFoo1() {
        return foo1;
    }

    public void setFoo1(Integer foo1) {
        this.foo1 = foo1;
    }

    public Integer getFoo2() {
        return foo2;
    }

    public void setFoo2(Integer foo2) {
        this.foo2 = foo2;
    }

}
Enter fullscreen mode Exit fullscreen mode

<1> the bean validation annotations created from the OpenAPI constraints.

custom bean validation annotation

Now we like to add a validation that checks the sum of the two Integer properties by writing @Sum(24).

Let's create the annotation

manually created custom bean validation annotation

package io.openapiprocessor.samples.validations;


import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Constraint (validatedBy = {FooSumValidator.class})
@Target ({ ElementType.TYPE, ElementType.PARAMETER })
@Retention (value = RetentionPolicy.RUNTIME)
public @interface Sum {
    String message () default "invalid sum";
    Class<?>[] groups () default {};
    Class<? extends Payload>[] payload () default {};

    int value();
}
Enter fullscreen mode Exit fullscreen mode

and the validation code.

manually created validation

package io.openapiprocessor.samples.validations;

import io.openapiprocessor.openapi.model.Foo;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class FooSumValidator implements ConstraintValidator<Sum, Foo> {
    private Integer sum;

    @Override
    public void initialize (Sum constraintAnnotation) {
        sum = constraintAnnotation.value ();
    }

    @Override
    public boolean isValid (Foo value, ConstraintValidatorContext context) {
        return value.getFoo1 () + value.getFoo2 () == sum;
    }
}

Enter fullscreen mode Exit fullscreen mode

mapping for the custom annotation

Now comes the interesting part, the annotation type mapping that tells the processor to add our custom annotation to the generated Foo pojo model class.

processor configuration mapping.yaml

openapi-processor-mapping: v2.1 # <1>

options:
  package-name: io.openapiprocessor.openapi
  bean-validation: true

map:
  types:
    # <2>
    - type: Foo @ io.openapiprocessor.samples.validations.Sum(24)
Enter fullscreen mode Exit fullscreen mode

<1> the new mapping version. Using another version will produce a warning that the mapping is invalid.

<2> the annotation mapping that tells the processor to @nnotate the Foo schema (Foo is the name of the OpenAPI schema) with the given annotation to the pojo model class generated for the Foo schema. The annotation is given with the fully qualified name (required to create the import) and (optionally) with fixed parameters.

model class with custom annotation

Now, with the annotation mapping in the configuration the processor will generate Foo like this:

generated file with custom annotation

package io.openapiprocessor.openapi.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;
import javax.validation.constraints.DecimalMin;
import io.openapiprocessor.samples.validations.Sum;

@Sum(24) // <1>
@Generated(value = "openapi-processor-spring", version = "2022.5")
public class Foo {

    @DecimalMin(value = "0")
    @JsonProperty("foo1")
    private Integer foo1;

    @DecimalMin(value = "-10")
    @JsonProperty("foo2")
    private Integer foo2;

    public Integer getFoo1() {
        return foo1;
    }

    public void setFoo1(Integer foo1) {
        this.foo1 = foo1;
    }

    public Integer getFoo2() {
        return foo2;
    }

    public void setFoo2(Integer foo2) {
        this.foo2 = foo2;
    }

}
Enter fullscreen mode Exit fullscreen mode

<1> our custom bean validation annotation.

summary

This little article described how to add a custom annotation to a generated class by adding an annotation type mapping to the processor mapping configuration.

To learn more about openapi-processor and how to generate controller interfaces and model classes from an OpenAPI description take a look at the documentation.

Top comments (0)