Annotation mapping

It is possible to add additional annotations to a source type. Currently, this is available as

  • global annotation type mapping:

    it adds an annotation to the model class generated for the source type.

  • global & endpoint parameter annotation mapping:

    it adds an annotation to a parameter of the source type or parameter name (this includes request body parameters).

The global annotation mapping should be added to the map/types or map/parameters section in the mapping.yaml.

The endpoint (http method) mapping restricts the mapping to a specific endpoint. This will go to the map/paths/<endpoint path>/parameters section in the mapping.yaml.

The annotation mapping is similar to other mappings and is defined like this:

type: {source type} @ {annotation type}
  • type is required.

    • {source type} is the type name used in the OpenAPI description and names the type that should receive the additional annotation. This can be a {type}:{format} combination like string:uuid.

    • {annotation type} is the fully qualified class name of the java annotation type.It may have parameters (see example below).

or with parameter name:

since 2024.1

name: {parameter name} @ {annotation type}
  • name is required.

    • {parameter name} is the name of the parameter in the generated interface that should receive the additional annotation.

    • {annotation type} is the fully qualified class name of the java annotation type.It may have parameters (see example below).

Here is a list of examples using different parameters:

 - type: Foo @ annotation.Bar
 - type: Foo @ annotation.Bar()
 - type: Foo @ annotation.Bar("bar")
 - type: Foo @ annotation.Bar(2)
 - type: Foo @ annotation.Bar(true)
 - type: Foo @ annotation.Bar(package.Foobar.class) (1)
 - type: Foo @ annotation.Bar(value = "bar", foo = 2)
1 since 2023.2 use a class as annotation parameter.

object source type

since 2023.3

it is also possible to add an annotation to all generated schema/model classes using a single annotation mapping:

 - type: object @ annotation

The object string represents all generated object classes (i.e. schema/model classes) and will add the given annotation only at the class level.

For example, a mapping like this:

map:
  types:
    - type: object @ lombok.Builder
@Builder
@Generated(...)
public class Foo {
   ...
}

The samples project has a small example using annotation mappings.

combining annotation mapping and type mapping

since 2023.1

Previously an annotation mapping was lost if the type was mapped at the same time to an existing class. It will now add the annotation to the existing class if possible.

Assume the following mapping:

openapi-processor-mapping: v8

options:

map:
  types:
    - type: Foo => openapiprocessor.MappedFoo
    - type: Foo @ annotation.Bar  (1)

  parameters:
     - type: Foo @ annotation.Bar (2)

MappedFoo is a class that is not generated. Adding an annotation at the parameter level works as expected (mapping <2>). But it is not possible to add the Bar annotation directly at the class (mapping <1>) like it is possible on a generated class:

@Bar
@Generated
public class Foo {
    // ....
}

instead, openapi-processor will add it on any MappedFoo property used in the generated model classes:

@Generated
public class FooBar {

    @Bar
    @JsonProperty("foo")
    private MappedFoo foo;

     // ....
}

mapping example

Given the following OpenAPI description, that describe two (echo like) endpoints that receive an object via post and return the same object. In the mapping file we add a custom bean validation annotation. It checks the sum of both properties in Foo and Bar.

openapi: 3.1.0
info:
  title: openapi-processor-spring sample api
  version: 1.0.0

paths:
  /foo:
    post:
      tags:
        - foo
      summary: annotation mapping example.
      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:
        '200':
          description: echo of the source parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Foo'

  /bar:
    post:
      tags:
        - bar
      summary: annotation mapping example.
      description: a simple endpoint where an annotation mapping is used on the request body
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Bar'
        required: true
      responses:
        '200':
          description: echo of the source parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Bar'

components:
  schemas:
    Foo:
      type: object
      properties:
        foo1:
          type: integer
          minimum: 0
        foo2:
          type: integer
          minimum: -10

    Bar:
      type: object
      properties:
        bar1:
          type: integer
        bar2:
          type: integer

and a mapping.yaml with annotation type mappings:

openapi-processor-mapping: v8 (1)

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

map:
  types:
    - type: Bar @ io.openapiprocessor.samples.validations.Sum(24) (2)

  parameters:
    - type: Foo @ io.openapiprocessor.samples.validations.Sum(value = 42) (3)

The Sum annotation in the example is a custom bean validation but the feature itself is not limited to bean validation.

1 use v2.1 (or later) as the mapping version to avoid validation warnings in the mapping file.
2 the Bar mapping is using a global type annotation mapping, so the annotation is added to the generated Bar class.
3 the Foo mapping adds the annotation to the parameter of the endpoint methods that use Foo.

Here are the generated interfaces, first the FooApi:

package io.openapiprocessor.openapi.api;

import io.openapiprocessor.openapi.model.Foo;
import io.openapiprocessor.samples.validations.Sum;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

public interface FooApi {

    /**
     * annotation mapping example.
     *
     * <p>a simple endpoint where an annotation mapping is used on the request body
     *
     * @return echo of the source parameter
     */
    @PostMapping(
            path = "/foo",
            consumes = {"application/json"},
            produces = {"application/json"})
    Foo postFoo(@RequestBody @Sum(value = 42) @Valid @NotNull Foo body); (1)

}
1 here is the additional annotation.

and the BarApi and the Bar class:

package io.openapiprocessor.openapi.api;

import io.openapiprocessor.openapi.model.Bar;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

public interface BarApi {

    /**
     * annotation mapping example.
     *
     * <p>a simple endpoint where an annotation mapping is used on the request body
     *
     * @return echo of the source parameter
     */
    @PostMapping(
            path = "/bar",
            consumes = {"application/json"},
            produces = {"application/json"})
    Bar postBar(@RequestBody @Valid @NotNull Bar body); (1)

}
1 no annotation here, mapping says it should be on the class:
package io.openapiprocessor.openapi.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.samples.validations.Sum;

@Sum(24) (1)
public class Bar {

    @JsonProperty("bar1")
    private Integer bar1;

    @JsonProperty("bar2")
    private Integer bar2;

    public Integer getBar1() {
        return bar1;
    }

    public void setBar1(Integer bar1) {
        this.bar1 = bar1;
    }

    public Integer getBar2() {
        return bar2;
    }

    public void setBar2(Integer bar2) {
        this.bar2 = bar2;
    }

}
1 and here it is :-)