object annotation mapping & lombok

27. July 2023

With openapi-processor-spring/micronaut release 2023.3 openapi-processor has a new mapping feature that allows it to add annotations to all generated model classes with a single mapping entry.

This is very useful in combination with lombok.

The article is based on the openapi-processor sample samples:spring-mvc-boot-3-lombok. The sample has a few more mapping entries.

the api

here is the sample api. Nothing clever, it is just for demonstration. :-)

It has a simple post endpoints that accepts a Foo schema. Foo has a few properties one of them a Bar schema.

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

paths:
  /foo:
    post:
      tags:
        - foo
      summary: echo a Foo.
      description: simple sample endpoint
      requestBody:
          $ref: '#/components/requestBodies/FooBody'
      responses:
        '200':
          description: foo
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Foo'

components:
  schemas:
    Foo:
      type: object
      properties:
        foo:
          type: string
          maxLength: 10
        id:
          type: string
          format: uuid
        bar:
          $ref: '#/components/schemas/Bar'

    Bar:
      type: object
      properties:
        bar:
          type: string
          maxLength: 20

  requestBodies:
    FooBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Foo'

no mapping

Without (annotation) mappings the configuration (mapping.yaml) is very simple:

simple mapping.yaml
openapi-processor-mapping: v4

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

openapi-processor(-spring) will generate an interface for the endpoint and two standard pojo classes for the schemas Foo and Bar.

Here is the generated interface:

FooApi.java interface
package io.openapiprocessor.openapi.api;

import io.openapiprocessor.openapi.model.Foo;
import io.openapiprocessor.openapi.support.Generated;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Generated(value = "openapi-processor-spring", version = "2023.3")
public interface FooApi {

    /**
     * echo a Foo.
     *
     * simple sample endpoint
     *
     * @return foo
     */
    @PostMapping(path = "/foo", consumes = {"application/json"}, produces = {"application/json"})
    Foo postFoo(@RequestBody(required = false) @Valid Foo body);

}

and the pojo model classes:

Foo.java pojo
package io.openapiprocessor.openapi.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;

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

    @Size(max = 10)
    @JsonProperty("foo")
    private String foo;

    @JsonProperty("id")
    private String id;

    @Valid
    @JsonProperty("bar")
    private Bar bar;

    // getter and setter
}

Bar.java is similar with different properties.

lombok

The generated classes doesn’t implement anything apart the getter and setters. No equals(), hashCode() or anything else.

openapi-processor does minimize the generated logic which would never really fit anyway.

If you need equals(), hashCode() or a Builder to make the model classes easier to use you can do that with lombok and the new object annotation mapping.

lombok object mapping

The object annotation mapping tells the processor to annotate all object s (i.e. all generated model classes) with the given annotation.

With annotation mapping the configuration has a few new lines:

object mapping.yaml
openapi-processor-mapping: v4

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

map:
  types:
    # annotates all generated model classes
    - type: object @ lombok.Builder (1)
    - type: object @ lombok.EqualsAndHashCode (2)
1 adds the lombok Builder annotation to all object s
2 adds the lombok EqualsAndHashCode annotation to all object s

Note that the annotation is given with package. The processor requires it to generate the import statements.

With the changes the generated model classes will now look like this:

Foo pojo with lombok
package io.openapiprocessor.openapi.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.EqualsAndHashCode;

@Builder (1)
@EqualsAndHashCode (1)
@Generated(value = "openapi-processor-spring", version = "2023.3")
public class Foo {

    @Size(max = 10)
    @JsonProperty("foo")
    private String foo;

    @JsonProperty("id")
    private String id;

    @Valid
    @JsonProperty("bar")
    private Bar bar;

    // getter and setter
}
Bar pojo with lombok
package io.openapiprocessor.openapi.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.EqualsAndHashCode;

@Builder (1)
@EqualsAndHashCode (1)
@Generated(value = "openapi-processor-spring", version = "2023.3")
public class Bar {

    @Size(max = 20)
    @JsonProperty("bar")
    private String bar;

    // getter and setter
}
1 the additional lombok annotations at the class level.

Now we can use the lombok builder and equals() / hashcode() with the model classes generated from the OpenAPI description.

Nice!

limitations?

This is a new feature, and we have to find out where it is useful and where not.

known limitations:

  • an object annotation will always add the given annotation at the class level of the generated class.

lombok specific:

  • the processor generates getters and setter itself, so the lombok @Getter and @Setter annotation can’t be used.

object mapping with parameters

The mapping will accept (simple) annotation parameters.

For example if you don’t like lombok’s default builder name, just add its annotation parameter to change it. Order of parameters or whitespaces do not matter.

object and parameter mapping.yaml
openapi-processor-mapping: v4

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

map:
  types:
    - type: object @ lombok.Builder(builderMethodName = "factory")

summary

This article shows how we can use an object annotation mapping to improve the model classes generated from the OpenAPI description with lomboks @Builder and @EqualsAndHashCode annotations.

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.