annotation mapping & bean-validation
Tags: openapi, java, springboot, micronaut
Published: 2022-10-16 15:44:06

The [openapi-processor-spring/micronaut release 2022.5](https://openapiprocessor.io/oap/home/new/spring-2022.5.html) 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](https://openapiprocessor.io) 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](https://openapiprocessor.io/spring/mapping/annotation.html) 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_
```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
```

## 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_
```java
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;
    }

}
```

<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_
```java
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();
}
```

and the validation code.

_manually created validation_
```java
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;
    }
}

```

## 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_
```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)
```

<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_
```java
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;
    }

}
```

<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](https://openapiprocessor.io).