What’s New

November 2024

  • openapi-processor-spring/micronaut 2024.6

trace mapping lookup

the processor can now create a log of the mapping lookup. It may help to understand failing mappings.

It adds two new options to control the logging.

openapi-processor-mapping: v10
options:
  package-name: # ...

map:
 # ...

logging:
  mapping: true
  mapping-target: stdout

logging.mapping enables the logging of the mapping lookups. mapping-target set the output channel. Simplest is to use stdout. The other possible value is logger. See the logging documentation for a more detailed description.

minimum/maximum and their exclusive version did not work for OpenAPI 3.1

fixed in the OpenAPI parser.

dependency updates

  • updated (internal) OpenAPI parser to 2024.6 (was 2024.5)

  • updated com.fasterxml.jackson:jackson-bom to 2.18.1 (was 2.18.0)

  • updated com.google.googlejavaformat:google-java-format to 1.24.0 (was 1.23.0)

October 2024

  • openapi-processor-spring/micronaut 2024.5

support for servers/server/url

it is now possible to tell the processor to generate a properties resource file with the path of a selected OpenAPI servers/server/url.

Given an OpenAPI description with a servers key:

openapi: 3.1.0
info:
  title: server url example
  version: 1.0.0

servers:
  - url: "https://openapiprocessor.io/{path}"
    variables:
      path:
        default: api
# ...

and a mapping

openapi-processor-mapping: v9
options:
  base-path:
    # false/true=0,1,2,... (default false)
    server-url: true

it will generate a properties file api.properties

openapi.base.path = /api

that can be used to configure the (Spring) context-path:

# application.properties

#spring.config.import = api.properties
server.servlet.context-path=${openapi.base.path}

See Server Url for more.

javadoc of record

a record should have its javadoc at the record using @param s to describe the record properties.

Instead of

/**
 * this is the <em>Foo</em> schema description
 */
@Generated(value = "openapi-processor-core", version = "test")
public record Foo(
    /**
     * <em>property</em> description
     */
    @JsonProperty("foo-bar")
    String fooBar
) {}

the processor now generates:

/**
 * this is the <em>Foo</em> schema description
 *
 * @param fooBar <em>property</em> description
 */
@Generated(value = "openapi-processor-core", version = "test")
public record Foo(
    @JsonProperty("foo-bar")
    String fooBar
) {}

warn on endpoint without success response

the processor ignores endpoints that have no success response (i.e. 2xx response code). To detect this "error" at compile time the processor will now print a warning with the effected endpoint.

type annotation mapping ignored with model-name-suffix

using a mapping like this:

openapi-processor-mapping: v9
options:
  model-name-suffix: Resource

maps:
  types:
    - type: Foo @ io.openapiprocessor.Annotation()

did not add the annotation because of the model-name-suffix.

dependency updates

  • updated (internal) OpenAPI parser to 2024.4 (was 2024.3)

  • updated com.fasterxml.jackson:jackson-bom from 2.17.1 to 2.18.0

  • updated com.google.googlejavaformat:google-java-format from 1.22.0 to 1.23.0

June 2024

  • openapi-processor-spring/micronaut 2024.4

add request body description to javadoc

The request body description is added as @param to the generated javadoc.

openapi: 3.1.0
info:
  title: javadoc
  version: v1

paths:
  /foo:
    get:
      requestBody:
        description: this is the request body
        # ...

missing @Generated

the generated Values and ValueValidator (used by enum-type string) were not annotated with @Generated.

missing import of class annotation parameter

using a .class parameter in a class annotation mapping did not add the import of the parameter class.

map:
  types:
    - type: Foo @ io.oap.ClassAnnotation(value = io.oap.Param.class)

In this example the import for Param was missing.

disable @Generated

its is now possible to disable the @Generated annotation. If it is disabled the processor will not add it to any generated type.

openapi-processor-mapping: v8

options:
  # ...

  # enable/disable generated annotation, true (default) or false.
  generated-annotation: false

control @JsonProperty annotation

By setting the json-property-annotation option is is possible to control the generation of the @JsonProperty annotation. It allows three values: always, auto or never.

  • always: (the default) adds a @JsonProperty annotation to all properties.

  • auto: only adds a @JsonProperty annotation to a property if it is required, i.e. if the OpenAPI property name is not a valid java identifier or if a property uses the readOnly/ writeOnly (OpenAPI) flags.

  • never: never adds a @JsonProperty annotation to the properties. This may generated invalid code if the property name is not a valid java identifier.

openapi-processor-mapping: v8

options:
  # ...

  # control @JsonProperty annotation, always (default), auto, never.
  json-property-annotation: auto

May 2024

  • openapi-processor-spring/micronaut 2024.3

response $ref did not work

using responses with `$ref`s did not work with all (supported) OpenAPI parsers.

  • internal OpenAPI parser did not work (the default parser).

  • openapi4j did not work (not maintained anymore).

  • Swagger parser worked.

It now works for all 3 (supported) OpenAPI parsers.

bad enum creation

the processor did not create a proper enum for an enum description like this:

components:
  schemas:
    AnEnum:
      type: string
      enum:
        - "1"
        - "2"

because 1 and 2 are not valid java identifiers, the processor generated

public enum Enum {
    INVALID("1"),
    INVALID("2");
    ...
}

The processor will now prefix invalid identifiers with "v" (value) to avoid this. The enum above will produce

public enum Enum {
    V1("1"),
    V2("2");
    ...
}

missing constraints with null mapping

using a null mapping:

openapi-processor-mapping: v7

options:
  bean-validation: true

map:
  paths:
    /foo:
      null: org.openapitools.jackson.nullable.JsonNullable

on a property

      properties:
        bar:
          nullable: true
          type: string
          maxLength: 4

did not add the constraint to the generated property.

dependency updates

  • updated (internal) OpenAPI parser to 2024.3 (was 2024.2)

  • updated swagger parser to 2.1.22 (was 2.1.21)

April 2024

  • openapi-processor-spring/micronaut 2024.2

setting the new compatibility options did not work

fixed setting the new compatibility options, it was always using the default values.

remove extra line feed in javadoc

removed the extra line feed (an empty line) in javadoc comments between summary and description.

optionally clear output directory

its is now possible to disable clearing of the targetDir when the processor is writing the generated files.

openapi-processor-mapping: v7

options:
  # ...

  # enable/disable deletion of targetDir: true (default) or false.
  clear-target-dir: false

February 2024

  • openapi-processor-spring/micronaut 2024.1

annotation mapping by OpenAPI extensions

it is now possible to use OpenAPI `x-`tensions to add additional annotations to schema properties:

Here is a simple schema that has x-`tensions on the `bar property.

openapi: 3.1.0
# ...
components:
  schemas:
    Foo:
      type: object
      properties:
        bar:
          type: string
          x-foo: single
          x-bar:
            - listA
            - listB

we can now map the `x-`tensions/values to annotations like this:

openapi-processor-mapping: v6
map:
  extensions:
    x-foo: single @ io.oap.FooA(value = "any")
    x-bar:
      - listA @ io.oap.FooB
      - listB @ io.oap.FooC
  1. which will generate the additional annotations on the property:

package generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import generated.support.Generated;
import io.oap.FooA;
import io.oap.FooB;
import io.oap.FooC;

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

    @FooA(value = "any")
    @FooB
    @FooC
    @JsonProperty("bar")
    private String bar;

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        this.bar = bar;
    }

}

annotation mapping by parameter name

another small improvement to annotation mapping is that we can add annotations by parameter name:

openapi-processor-mapping: v6
map:
  parameters:
    - name: foo @ annotation.Foo

reactive bean validation

the position of the @Valid annotation on reactive types has changed.

Until now the @Valid was placed on the generic type of the reactive wrapper, like this:

    @Mapping("/foo-flux")
    void postFooFlux(@Parameter Flux<@Valid Bar> body);

but validation did not happen with Spring. Spring needs the @Valid on the reactive wrapper to trigger the validation. Therefore @Valid is now placed by default on the reactive wrapper:

    @Mapping("/foo-flux")
    void postFooFlux(@Parameter @Valid Flux<Bar> body);

It should only take a bit annotation clean up on the interface implementations to adapt your code to the new @Valid position.

keeping the old behavior

To postpone the update, set the bean-validation-valid-on-reactive option to false.

openapi-processor-mapping: v6

options:
  # ...

compatibility:
  # optional, default is true
  bean-validation-valid-on-reactive: false

I would like to remove this option in the future. If you still need the old @Valid position please create an issue to help me understand why the old @Valid position is still useful.

identifier word breaks

the processor does now recognize a change from letter to number as a word break. The improves generation of camel case identifiers.

given an identifier from the OpenAPI description, the processor would generate the following names for different kinds of identifiers:

OpenAPI camel case variable class enum

new

foo2Bar

foo2Bar

foo2Bar

Foo2Bar

FOO2_BAR

old

foo2Bar

foo2bar

foo2bar

Foo2bar

FOO2BAR

keeping the old behavior

To postpone the update, set the identifier-word-break-from-digit-to-letter option to false.

openapi-processor-mapping: v6

options:
  # ...

compatibility:
  # optional, default is true
  identifier-word-break-from-digit-to-letter: false

Support Mono<ResponseEntity> as result type

previous versions allowed to configure a result wrapper (e.g. Spring ResponseEntity) and reactive types via single and multi mapping.

openapi-processor-mapping: v6

options:
   # ...

map:
  result: org.springframework.http.ResponseEntity

  single: reactor.core.publisher.Mono
  multi: reactor.core.publisher.Flux

Using both always wraps the reactive types with the result type. For example with Spring ResponseEntity (result type) and the reactor types Mono and Flux as

ResponseEntity<Mono<...>>
ResponseEntity<Flux<...>>

Unfortunately if you need the reactive result to modify the http response, something like this:

// does not work
public ResponseEntity<Mono<Result>> someEndpoint() {
    return someBean.getResult()
           .map(r -> ResponseEntity
                   .ok()
                   .eTag(r.eTag())
                   .body(Mono.just(r)));
}

it will not work because the final type of the statement is Mono<ResponseEntity<Mono<Result>>> and not the expected ResponseEntity<Mono<Result>>.

With this release we can fix that by setting the result mapping to

openapi-processor-mapping: v6

options:
  # ...

map:
  # wrap the ResponseEntity with Mono
  result: reactor.core.publisher.Mono<org.springframework.http.ResponseEntity>

  single: reactor.core.publisher.Mono
  multi: reactor.core.publisher.Flux

which will generate the endpoint signature as

public Mono<ResponseEntity<Mono<Result>>> someEndpoint() {
   // ...
}

and the above code will now work.

It is recommended to configure this on the endpoint level if you just need this for a few endpoints.

See also Spring ResponseEntity documentation.

November 2023

  • openapi-processor-spring/micronaut 2023.6

support different enum styles

it is now possible to (globally) configure different enum types in mapping.yaml:

openapi-processor-mapping: v5

options:
  package-name: generated
  enum-type: default|string|framework

default, which is default, creates a simple java enum with all uppercase enum values. It will create the same code as previous versions.

string, simply uses String and does not create an enum class. This is useful if automatic conversion of the incoming value to a java enum value does not work. In case bean-validation is enabled the processor adds a (generated) validation annotation that verifies that the string is a valid (OpenAPI) enum value.

public interface FooApi {
    @Mapping(path = "/foo", produces = {"application/json"})
    Foo postFoo(@Parameter(name = "enum") @Values(values = {"one", "two"}) String aEnum);
}

framework, is a placeholder for framework specific enum generation, only supported by openapi-processor-spring.

It generates a Java enum class similar to the default enum type. In addition, it generates a Spring ConverterFactory that Spring can use to convert incoming enum values to its corresponding java enum value (if passed as a parameter, not as part of the body).

To enable the ConverterFactory, create a configuration class like this:

package io.openapiprocessor.samples;

import {package-name}.spring.StringToEnumConverterFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @SuppressWarnings("rawtypes")
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverterFactory(new StringToEnumConverterFactory());
    }
}

This registration is one time task. The converter factory does create an enum converter for all generated enum classes.

There is a sample that is using different enum-type configurations (string & framework).

October 2023

  • openapi-processor-spring/micronaut 2023.5

support primitive types

it is now possible to use primitive types in mapping.yaml:

openapi-processor-mapping: v4

options:
  package-name: generated

map:
  types:
    - type: string:binary => byte[]

September 2023

  • openapi-processor-spring/micronaut 2023.4

json schema validation for OpenAPI 3.1

the processor does now run json schema validation on OpenAPI 3.1 documents.

automatically clear output directory

when called, the processor clears the target directory to avoid any left over/obsolete files from previous runs.

accept generic wildcard in mapping

it is now possible to use the generic ? in a mapping.

openapi-processor-mapping: v4
options:
  package-name: io.openapiprocessor.generated

map:
  parameters:
     - add: foo => io.openapiprocessor.GenericType<?>

Spring: missing import

Spring doesn’t have shortcut annotations, e.g. @GetMapping instead of @RequestMapping(…​, method = RequestMethod.HEAD) for the http methods HEAD, OPTIONS & TRACE.

The processor does now add the missing import of RequestMethod.

improved json schema of mapping.yaml

when using endpoint specific mappings the processor (sometimes) complained about invalid entries in the mapping.yaml. This was caused by wrong definitions in the corresponding json schema.

don’t request json schema draft

a miss-configuration of the json-schema validator caused an unnecessary network request to download a json schema (draft 7) from https://json-schema.org.

write http operations in original order

the controller endpoint methods for the http operations are written in the same order as in the OpenAPI description.

Unfortunately this doesn’t work with the swagger OpenAPI parser because it doesn’t preserve the original order.

July 2023

  • openapi-processor-spring/micronaut 2023.3

object @ annotation

Using the object keyword it is possible to add an annotation to all generated schema/model classes using a single annotation mapping:

map:
  types:
    - type: object @ lombok.Builder

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

@Builder
@Generated(...)
public class Foo {
   ...
}

java records

openapi-processor is now capable of generating java record s instead of pojos for schemas. This is a global setting in the mapping.yaml. It can either have the value default (which is default) to generate pojos or record to generate records.

openapi-processor-mapping: v4

options:
  model-type: record

With model-type: record the processor will generate record s like this:

package generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import generated.support.Generated;

@Generated(value = "openapi-processor-core", version = "test")
public record Foo(
    @JsonProperty("bar")
    String bar
) {}

and without model-type or model-type: default it will create a simple pojo:

package generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import generated.support.Generated;

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

    @JsonProperty("bar")
    private String bar;

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        this.bar = bar;
    }

}

java-format option

openapi-processor uses google-java-format to format the generated files (including javadoc). Unfortunately it depends on internal jdk packages that are strongly encapsulated since JDK 16. It is necessary to tell the jdk to export a few packages.

In theory, it is not hard to configure it but in real life it is a bit fiddly to get this working. To get started without fighting with it, the default is now false instead of true.

To (re-)enable code formatting add the format-code option to the mapping.yaml:

openapi-processor-mapping: v4

options:
  format-code: true

June 2023

  • openapi-processor-spring/micronaut 2023.2

nested generic types

It is now possible to create type mappings with nested generics types. Here are a few examples:

openapi-processor-mapping: v3

options:
  package-name: generated

map:
  types:
    - type: Foo => java.util.Map<java.lang.String, java.util.List<java.lang.String>>

  paths:
    /foo:
      responses:
        - content: application/json => java.util.Map<java.lang.String, java.lang.String>

    /foo2:
      responses:
        - content: application/json => java.util.Map<java.lang.String, java.util.List<java.lang.String>>

This is useful to map an OpenAPI dictionary description using additionalProperties to a proper java map type:

# a schema the defines a dictionary with string keys and string values
Map:
    type: object
    additionalProperties:
      type: string

annotation mapping allows class parameter

annotation mapping now accepts a java class type as parameter. It is possible to add a mapping like this:

openapi-processor-mapping: v3

map:
  types:
    - type: string:foo @ io.oap.Annotation (value = io.oap.Bar.class)

improved validation output

schema validation by the internal parser has simpler & better output based on the JSON schema basic output format. It is not perfect but it is getting better.

It will provide better help on where the error is, but it may report multiple ambiguous errors.

If a schema property uses anyOf or oneOf and all possibilities don’t match (e.g. because there is a spelling error) the validator can’t know which one was meant and complains about all of them.

An example:

the error the value does not validate against the 'false' schema at instance …​ usually means that a property has a spelling error.

If the OpenAPI allows a $ref at the same location the validator reports a second error should have a property '$ref' at instance …​ because a reference object must have a $ref property.

January 2023

  • openapi-processor-spring/micronaut 2023.1

support requestBody $ref

the processor is now able to resolve $ref s of requestBody (This works with all 3 OpenAPI parsers).

openapi: 3.1.0
info:
  title: components requestBodies
  version: '1.0'

paths:
  /foo:
    post:
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: string
      requestBody:
        $ref: '#/components/requestBodies/Foo'  (1)

components:
  requestBodies:
    Foo:
      content:
        application/json:
          schema:
            type: object
            properties:
              foo:
                type: string
1 $ref is direct child of requestBody.

annotation mapping support for simple data types

it is now possible to add an annotation mapping for simple data types (format works too):

openapi-processor-mapping: v3

map:
  types:
    - type: string:uuid => java.util.UUID
    - type: string:uuid @ annotation.Bar

openapi-processor will add it on any string:uuid property used in the generated model classes:

@Generated
public class Foo {

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

     // ....
}

annotation mapping support for mapped types

in the previous version 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: v3

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>):

@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;

     // ....
}

bean validation v3 support

Spring Boot 3 updates bean validations to v3. In v3 the package name changed from javax to jakarta. It is now possible to select between the v2 & v3 version in the mapping.yaml.

the new mapping schema v3 adds javax and jakarta as possible values for the bean-validation option. true/false will still work as before.

# use v3 for proper validation of the mapping file
openapi-processor-mapping: v3

options:
  # no bean validation, as before
  bean-validation: false

  # enable bean validation, as before (will use `javax...`)
  bean-validation: true

  # new: enable bean validation with `javax...`
  bean-validation: javax

  # new: enable bean validation with `jakarta...`
  bean-validation: jakarta

bean validation support on mapped data types

openapi-processor now preserves bean validation annotations when the source data type is mapped to an existing class. This is most interesting for the @Valid annotation.

It adds the annotations it would add on the source data type. In previous versions the annotations got lost when the data type was mapped to an existing class. Without`@Valid` the validation would not be triggered on the mapped object.

having this OpenAPI description

openapi: 3.1.0
info:
  title: mapped bean validation
  version: 1.0.0

paths:
  /foo:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Foo'
      responses:
        204:
          description: none

components:
  schemas:
    Foo:
      type: object
      properties:
        foo:
          type: integer
          minimum: 0

the endpoint looks like this without a mapping that replaces Foo (ignore the @Mapping/@Parameter annotations, this is pseudocode used by the integration tests):

package generated.api;

import annotation.Mapping;
import annotation.Parameter;
import generated.model.Foo;
import javax.validation.Valid;

public interface Api {

    @Mapping("/foo")
    void postFoo(@Parameter @Valid Foo body);   // has @Valid annotation

}

with a mapping that replaces Foo with Bar

openapi-processor-mapping: v3

options:
  package-name: generated
  bean-validation: true

map:
  types:
    - type: Foo => openapiprocessor.Bar

it will now generate the endpoint with a @Valid on the mapped data type.

package generated.api;

import annotation.Mapping;
import annotation.Parameter;
import javax.validation.Valid;
import openapiprocessor.Bar;

public interface Api {

    @Mapping("/foo")
    void postFoo(@Parameter @Valid Bar body);   // new: has @Valid annotation

}