Наследование при генерации кода с OpenAPI Generator
Имеется Open API v.3.0.2 полученный от заказчика и описывающий его REST API. На основе полученного API (читай swagger.json) генерируется Java клиент с помощью Maven-плагина org.openapitools:openapi-generator-maven-plugin:6.1.0. В спецификации присутствует наследование, т.е. тип/schema LineElement наследуется типом/schem'ой LineMachineElement (приведён только один пример наследования, но на самом деле их много).
Проблема в том, что при кодогенерации родительский класс LineElement устанавливает поля приватными (тут пока ничего удивительного), но вот классы наследники в конструкторах пытаются получить к ним доступ. В официальной документации имеется пару слов по этому поводу, но описание скудное, либо я что-то недопонимаю.
Если я в описании API (swagger.json) меняю свойства readOnly у полей LineElement на false, то кодогенерация выполняется без проблем: в сгенерированном родительском классе присутствуют setter'ы, а в наследниках конструкторы больше не генерируются.
Можно, конечно, отредактировать API, но в этом случае возникает вопрос: ошибка в моей некорректной конфигурации кодогенератора? Или проблема в описании API?
Каким образом сконфигурировать/указать кодогенератору на необходимость установки полей в protected при наследовании?
Ссылки по теме
- Спецификация Open API v.3.0.2
- Композиция и полиморфизм в Open API v.3.0.2
- Документация к Maven-плагину
- Документация по настройке Java-генератора
Конфигурация и файлы
Фрагмент REST API (swagger.json)
{
"openapi": "3.0.1",
"info": "...",
"paths": "...",
"components": {
"schemas": {
"ElementUILayout": {
"type": "object",
"properties": {
"column": {
"type": "integer",
"description": "...",
"format": "int32",
"nullable": true,
"readOnly": true
},
"row": {
"type": "integer",
"description": "...",
"format": "int32",
"nullable": true,
"readOnly": true
}
},
"additionalProperties": false,
"description": "Information about the layout for the UI."
},
"LineElement": {
"required": [
"id",
"layout",
"name",
"type",
"url"
],
"type": "object",
"properties": {
"type": {
"minLength": 1,
"type": "string",
"description": "...",
"readOnly": true,
"example": "magazine"
},
"id": {
"minLength": 1,
"type": "string",
"description": "...",
"readOnly": true,
"example": "..."
},
"name": {
"minLength": 1,
"type": "string",
"description": "...",
"readOnly": true,
"example": "..."
},
"url": {
"minLength": 1,
"type": "string",
"description": "...",
"readOnly": true,
"example": "..."
},
"image": {
"type": "string",
"description": "...",
"nullable": true,
"readOnly": true,
"example": "..."
},
"layout": {
"$ref": "#/components/schemas/ElementUILayout"
}
},
"additionalProperties": false,
"description": "...",
"discriminator": {
"propertyName": "type",
"mapping": {
"machine": "#/components/schemas/LineMachineElement",
"...": "..."
}
}
},
"LineMachineElement": {
"type": "object",
"allOf": [{
"$ref": "#/components/schemas/LineElement"
}
],
"additionalProperties": false,
"description": "..."
}
}
}
}
Используемая конфигурация Maven-плагина:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.abc.xyz</groupId>
<artifactId>qwerty_xxx</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<maven.compiler.release>${java.version}</maven.compiler.release>
<!-- Other unnecessary properties -->
</properties>
<dependencies>
<!-- slf4j, jersey, etc. -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>6.1.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/swagger.json</inputSpec>
<generatorName>java</generatorName>
<apiPackage>${groupId}.openapi.api</apiPackage>
<modelPackage>${groupId}.openapi.model</modelPackage>
<invokerPackage>${groupId}.openapi.invoker</invokerPackage>
<configOptions>
<delegatePattern>true</delegatePattern>
<library>jersey2</library>
<fullJavaUtil>true</fullJavaUtil>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
<!-- Other unnecessary plugins -->
</plugins>
</build>
</project>
Сокращённые сгенерированные классы.
LineElement.java
@ApiModel(description = "...")
@JsonPropertyOrder({
LineElement.JSON_PROPERTY_TYPE,
LineElement.JSON_PROPERTY_ID,
LineElement.JSON_PROPERTY_NAME,
LineElement.JSON_PROPERTY_URL,
LineElement.JSON_PROPERTY_IMAGE,
LineElement.JSON_PROPERTY_LAYOUT
})
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "...")
@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = LineMachineElement.class, name = "LineMachineElement"),
@JsonSubTypes.Type(value = LineMachineElement.class, name = "machine")
/* остальные подтипы пропущены для экономии места */
})
public class LineElement {
public static final String JSON_PROPERTY_TYPE = "type";
private String type;
public static final String JSON_PROPERTY_ID = "id";
private String id;
public static final String JSON_PROPERTY_NAME = "name";
private String name;
public static final String JSON_PROPERTY_URL = "url";
private String url;
public static final String JSON_PROPERTY_IMAGE = "image";
private JsonNullable<String> image = JsonNullable.<String>undefined();
public static final String JSON_PROPERTY_LAYOUT = "layout";
private ElementUILayout layout;
public LineElement() { }
@javax.annotation.Nonnull
@ApiModelProperty(example = "...", required = true, value = "...")
@JsonProperty(JSON_PROPERTY_TYPE)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public String getType() {
return type;
}
@javax.annotation.Nonnull
@ApiModelProperty(example = "...", required = true, value = "...")
@JsonProperty(JSON_PROPERTY_ID)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public String getId() {
return id;
}
@javax.annotation.Nonnull
@ApiModelProperty(example = "...", required = true, value = "...")
@JsonProperty(JSON_PROPERTY_NAME)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public String getName() {
return name;
}
@javax.annotation.Nonnull
@ApiModelProperty(example = "...", required = true, value = "...")
@JsonProperty(JSON_PROPERTY_URL)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public String getUrl() {
return url;
}
@javax.annotation.Nullable
@ApiModelProperty(example = "...", value = "...")
@JsonIgnore
public String getImage() {
return image.orElse(null);
}
@JsonProperty(JSON_PROPERTY_IMAGE)
@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
public JsonNullable<String> getImage_JsonNullable() {
return image;
}
@JsonProperty(JSON_PROPERTY_IMAGE)
private void setImage_JsonNullable(JsonNullable<String> image) {
this.image = image;
}
public LineElement layout(ElementUILayout layout) {
this.layout = layout;
return this;
}
@javax.annotation.Nonnull
@ApiModelProperty(required = true, value = "")
@JsonProperty(JSON_PROPERTY_LAYOUT)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public ElementUILayout getLayout() {
return layout;
}
@JsonProperty(JSON_PROPERTY_LAYOUT)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
public void setLayout(ElementUILayout layout) {
this.layout = layout;
}
/* equals, equalsNullable, hashCode, hashCodeNullable, toString, toIndentedString */
static {
// Initialize and register the discriminator mappings.
Map<String, Class<?>> mappings = new HashMap<String, Class<?>>();
mappings.put("LineMachineElement", LineMachineElement.class);
mappings.put("machine", LineMachineElement.class);
mappings.put("LineElement", LineElement.class);
// остальные маппинги опущены для экономии места
JSON.registerDiscriminator(LineElement.class, "type", mappings);
}
}
LineMachineElement.java
@ApiModel(description = "...")
@JsonPropertyOrder({})
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "...")
@JsonIgnoreProperties(
value = "type", // ignore manually set type, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = LineMachineElement.class, name = "machine")
/* остальные подтипы опущены для экономии места */
})
public class LineMachineElement extends LineElement {
public LineMachineElement() {}
@JsonCreator
public LineMachineElement(
@JsonProperty(JSON_PROPERTY_TYPE) String type,
@JsonProperty(JSON_PROPERTY_ID) String id,
@JsonProperty(JSON_PROPERTY_NAME) String name,
@JsonProperty(JSON_PROPERTY_URL) String url,
@JsonProperty(JSON_PROPERTY_IMAGE) String image
) {
this();
// попытка доступа к частным полям родительского класса
this.type = type;
this.id = id;
this.name = name;
this.url = url;
this.image = image;
}
/* equals, equalsNullable, hashCode, hashCodeNullable, toString, toIndentedString */
static {
// Initialize and register the discriminator mappings.
Map<String, Class<?>> mappings = new HashMap<String, Class<?>>();
mappings.put("machine", LineMachineElement.class);
mappings.put("LineMachineElement", LineMachineElement.class);
// остальные маппинги опущены для экономии места
JSON.registerDiscriminator(LineMachineElement.class, "type", mappings);
}
}