openapi-processor-spring type mapping
what is type mapping and how do I map OpenAPI schemas to Java types?
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 and conversion to json.
See the documentation for more. There is also a playground to preview the processors.
openapi-processor-spring
openapi-processor-spring (oap-spring) generates server side Java code for Spring Boot from an OpenAPI description. It generates interfaces for the endpoints with all the required Spring annotations, and it generates model classes (simple POJOs) with Jackson annotations for the OpenAPI schemas used in those endpoints.
By creating a controller that implements a generated interface it gets easier to implement the expected endpoints. Spring will automatically pick up all annotations on the generated interface methods. You can concentrate on the implementation.
what is type mapping?
Code generation is tricky. It is impossible to generate the perfect code just from the OpenAPI description. There are many cases where the generator needs our help to generate the code we like.
Just think of collections. OpenAPI has a single schema abstraction to describe collections: the array. It does not need more.
Using array (e.g. String[]) in Java is possible (and that is what oap-spring is using by default) but usually we prefer a more powerful and easier to use collection like List or Set etc.
That is why we need type mapping. Type mapping allows us to map an OpenAPI schema to a specific Java class.
The generator does use the mapping to replace any occurrence of the given OpenAPI schema in the generated code with the mapped target Java type. It doesn’t matter if it is a parameter, response or a property of an object schema.
If an OpenAPI model schema (which would normally generate a POJO class) gets mapped to an existing Java class oap-spring will not generate a (duplicate) POJO class.
where to specify type mappings?
Type mapping is part of the openapi-processor yaml configuration file mapping.yaml. It should be a sibling of the OpenAPI yaml files (i.e. in the same directory).
The file looks like this:
openapi-processor-mapping: v2 # (1)
options:
package-name: io.openapiprocessor.mapping # (2)
map:
# java type mappings # (3)
-
sets the configuration file format. It must be present with version 2 to use the mapping format described below.
-
sets the Java package for the generated source files.
-
mapis the parent key of all type mappings.
how to specify a type mapping?
Type mapping uses a simple notation to specify a mapping:
{collection}: # (1)
{key}: {source type} => {target type} # (2)
-
this defines the mapping collection. All mappings are grouped into a couple of fixed collections:
types,parameters,responsesandpaths. A mapping collection can have any number of mappings. -
keydefines what kind of mapping gets defined. For exampletypeis the key for a global type mapping in thetypescollection. The value of the key maps a source type to a destination type by using an=>arrow as the mapping operator.{source type}is the name of an OpenAPI type and{target type}is the fully qualified Java class name. Fully qualified because the processor needs the package name to generate an import statement for the target type.
Let’s take a look at a few mapping examples. This is just an introduction and doesn’t describe all the possibilities. See the type mapping documentation for more.
type mapping examples
global mapping
The first mapping is the global type mapping. Remembering the array example from above we can change it globally to java.util.Collection:
map:
types: # (1)
- type: array => java.util.Collection # (2)
- type: Foo => io.openapiprocessor.samples.Foo # (3)
-
typesis the list of global type mappings. It is global because it is a direct child of themapkey. -
- typeis an actual mapping item, and it says: map the OpenAPIarrayschema type to Java’sjava.util.Collection.Importantthe processor assumes that the Java target of
arrayhas a single generic parameter, and it will automatically use thearray'sitemproperty type as the generic parameter. -
this one maps an OpenAPI schema
Footo an existing Java class in our code base.
path mapping
The second mapping is path-based type mapping and it is quite useful. It allows us to limit a mapping rule to a single endpoint. It is not used for any other endpoint.
map:
types:
- type: array => java.util.Collection # (1)
paths: # (2)
/foo:
types:
- type: array => java.util.List # (3)
-
the global array mapping from the previous example.
-
pathsis a map, that allows us to add mappings for specific endpoints. Each key (like/foo) is an endpoint path. This is similar to the OpenAPI description itself. -
a path based mapping. It will override the global array mapping and use
Listinstead of theCollectionfrom the global mapping for the/fooendpoint method.
parameter & response mapping
The third and fourth mapping collections are mapping by parameter name and by response content:
map:
parameters: # (1)
- name: date => java.time.ZonedDateTime # (2)
responses: # (3)
- content: application/vnd.foo => io.openapiprocessor.samples.Foo # (4)
-
parametersis the list of global parameter mappings. -
- nameadds a mapping, this one says: (globally) map all parameters with namedateto the Java typejava.time.ZonedDateTime. -
responsesis the list of global response mappings. -
-contentadds a mapping, and it says: map the content typeapplication/vnd.footo the Java typeio.openapiprocessor.samples.Foo.
It is not clear yet how useful these two are at the global level. The date parameter is for example easier to handle with a simple global type mapping like this:
map:
types:
- type: string:date-time => java.time.ZonedDateTime # (1)
-
maps the primitive
stringtype with the formatdate-timeto the Java classjava.time.ZonedDateTime.
Both mappings are available as path based mappings and that’s a lot more useful than the global parameter & response mapping. It can be used to override global mappings for a specific endpoint:
map:
paths:
/foo:
parameters:
- name: date => java.time.ZonedDateTime
responses:
- content: application/vnd.foo => io.openapiprocessor.samples.Foo
add parameter mapping
The last example is the most useful part of path based parameter mapping: parameter add mapping. It allows us to add parameters to an endpoint which are not part of the OpenAPI description.
This allows us to pass "technical" parameters to an endpoint we want to implement:
map:
paths:
/foo:
parameters:
- add: processingId => java.lang.String # (1)
- add: request => javax.servlet.http.HttpServletRequest # (2)
-
add a
String processingIdparameter to the controller method that is provided by anHandlerMethodArgumentResolver. -
add a
HttpServletRequest requestparameter to the controller method, so we can look at all the details of the request.
there is more…
This was just a small introduction into the mapping feature of openapi-processor-spring. See the type mapping documentation for a more complete description.
That’s it.