Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,13 @@ Example:
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/required-properties.yaml -o /tmp/java-okhttp/ --openapi-normalizer REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT=true
```

- `REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING``: when set to true, oneOf is removed and is converted into mappings in a discriminator mapping.

Example:
```
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g spring -i modules/openapi-generator/src/test/resources/3_0/spring/issue_23527.yaml -o /tmp/java-spring/ --openapi-normalizer REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING=true
```

- `FILTER`

The `FILTER` parameter allows selective inclusion of API operations based on specific criteria. It applies the `x-internal: true` property to operations that do **not** match the specified values, preventing them from being generated. Multiple filters can be separated by a semicolon.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ public class OpenAPINormalizer {
// are removed as most generators cannot handle such case at the moment
final String REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY = "REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY";

// when set to true, oneOf is removed and is converted into mappings in a discriminator mapping
final String REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING = "REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING";


// when set to true, oneOf/anyOf with either string or enum string as sub schemas will be simplified
// to just string
final String SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING = "SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING";
Expand Down Expand Up @@ -214,6 +218,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
ruleNames.add(SIMPLIFY_ONEOF_ANYOF_ENUM);
ruleNames.add(REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT);
ruleNames.add(SORT_MODEL_PROPERTIES);
ruleNames.add(REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING);

// rules that are default to true
rules.put(SIMPLIFY_ONEOF_ANYOF, true);
Expand Down Expand Up @@ -1053,6 +1058,8 @@ protected Schema normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
// simplify first as the schema may no longer be a oneOf after processing the rule below
schema = processSimplifyOneOf(schema);

schema = processReplaceOneOfByMapping(schema);

// if it's still a oneOf, loop through the sub-schemas
if (schema.getOneOf() != null) {
for (int i = 0; i < schema.getOneOf().size(); i++) {
Expand Down Expand Up @@ -1569,6 +1576,33 @@ protected Schema processSimplifyOneOf(Schema schema) {
return schema;
}


protected Schema processReplaceOneOfByMapping(Schema schema) {
if (!getRule(REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING)) {
return schema;
}

if (schema.getDiscriminator() != null) {
Discriminator discriminator = schema.getDiscriminator();
if (discriminator.getMapping() == null) {
Map<String, String> mapping = new TreeMap<>();
discriminator.setMapping(mapping);
for (Object oneOfObject : schema.getOneOf()) {
Schema oneOf = (Schema) oneOfObject;
String ref = oneOf.get$ref();
if (ref != null) {
String name = ref.contains("/") ? ref.substring(ref.lastIndexOf('/') + 1) : ref;
mapping.put(name, oneOf.get$ref());
}
}
}

schema.setOneOf(null);
}
return schema;
}


/**
* Set nullable to true in array/set if needed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package org.openapitools.codegen;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.util.Yaml;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
Expand Down Expand Up @@ -1502,4 +1505,29 @@ public Schema normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
}
}

@Test
public void testREPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING() {
// to test array schema processing in 3.1 spec
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/spring/issue_23527.yaml");

Map<String, String> inputRules = Map.of("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true");
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, inputRules);
openAPINormalizer.normalize();
dump(openAPI);
}

private void dump(OpenAPI openAPI) {

ObjectMapper mapper = Yaml.mapper();
String yaml = null;
try {
yaml = mapper.writeValueAsString(openAPI);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}

System.out.println(yaml);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import static org.openapitools.codegen.CodegenConstants.*;
import static org.openapitools.codegen.TestUtils.*;
import static org.openapitools.codegen.languages.JavaClientCodegen.*;
import static org.openapitools.codegen.languages.SpringCodegen.SPRING_BOOT;
import static org.testng.Assert.*;

public class JavaClientCodegenTest {
Expand Down Expand Up @@ -4325,4 +4326,33 @@ public void testJspecify(String library, boolean useSpringBoot4, boolean hasJspe
.fileContains("@org.jspecify.annotations.NullMarked");

}

@DataProvider(name = "replaceOneOf")
public Object[][] replaceOneOf() {
return new Object[][]{
{"src/test/resources/3_0/spring/issue_23527.yaml"},
{"src/test/resources/3_0/spring/issue_23527_1.yaml"},
{"src/test/resources/3_0/spring/issue_23527_2.yaml"}
};
}

@Test(dataProvider = "replaceOneOf" )
void replaceOneOfByDiscriminatorMapping(String file) throws IOException {
Map<String, File> files = generateFromContract(file, APACHE, Map.of(),
codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true"));

JavaFileAssert.assertThat(files.get("GeoJsonObject.java"))
.isNormalClass()
.doesNotExtendsClasses()
.fileContains("String type")
.fileDoesNotContain("coordinates")
.assertTypeAnnotations()
.containsWithName("JsonSubTypes");

JavaFileAssert.assertThat(files.get("Polygon.java"))
.extendsClass("GeoJsonObject")
.doesNotImplementInterfaces("GeoJsonObject")
.fileContains("List<Double> coordinates");

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ public JavaFileAssert isNormalClass() {
return this;
}

public JavaFileAssert extendsClass(String... parentClass) {
Set<String> expectedClasses = Stream.of(parentClass)
.collect(Collectors.toSet());

Set<String> actualParents = actual.getType(0)
.asClassOrInterfaceDeclaration().getExtendedTypes()
.stream()
.map(ClassOrInterfaceType::getNameWithScope)
.collect(Collectors.toSet());

Assertions.assertThat(actualParents)
.withFailMessage("Expected type %s to extends %s, but found %s",
actual.getType(0).getName().asString(), expectedClasses, actualParents)
.isEqualTo(expectedClasses);
return this;
}

public JavaFileAssert doesNotExtendsClasses() {
Set<String> actualParents = actual.getType(0)
.asClassOrInterfaceDeclaration().getExtendedTypes()
.stream()
.map(ClassOrInterfaceType::getNameWithScope)
.collect(Collectors.toSet());
Assertions.assertThat(actualParents)
.withFailMessage("Expected type %s to extends a class, but found %s",
actual.getType(0).getName().asString(), actualParents)
.isEmpty();
return this;
}

public JavaFileAssert implementsInterfaces(String... implementedInterfaces) {
Set<String> expectedInterfaces = Stream.of(implementedInterfaces)
.collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6664,4 +6664,35 @@ public void testJspecify(String library, int springBootVersion, String fooApiFil
JavaFileAssert.assertThat(files.get("model/package-info.java"))
.fileContains("@org.jspecify.annotations.NullMarked");
}


@DataProvider(name = "replaceOneOf")
public Object[][] replaceOneOf() {
return new Object[][]{
{"src/test/resources/3_0/spring/issue_23527.yaml"},
{"src/test/resources/3_0/spring/issue_23527_1.yaml"},
{"src/test/resources/3_0/spring/issue_23527_2.yaml"}
};
}

@Test(dataProvider = "replaceOneOf" )
void replaceOneOfByDiscriminatorMapping(String file) throws IOException {
Map<String, File> files = generateFromContract(file, SPRING_BOOT, Map.of(),
codegen -> codegen.addOpenapiNormalizer("REPLACE_ONE_OF_BY_DISCRIMINATOR_MAPPING", "true"));

JavaFileAssert.assertThat(files.get("GeoJsonObject.java"))
.isNormalClass()
.doesNotExtendsClasses()
.fileContains("String type")
.fileDoesNotContain("coordinates")
.assertTypeAnnotations()
.containsWithName("JsonSubTypes")
;

JavaFileAssert.assertThat(files.get("Polygon.java"))
.extendsClass("GeoJsonObject")
.doesNotImplementInterfaces("GeoJsonObject")
.fileContains("List<Double> coordinates");

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
openapi: 3.0.3
info:
title: GeoJSON Discriminator Test
version: 1.0.0
paths: {}
components:
schemas:
GeoJsonObject:
type: object
properties:
type:
type: string
required:
- type
discriminator:
propertyName: type
oneOf:
- $ref: '#/components/schemas/Polygon'
- $ref: '#/components/schemas/MultiPolygon'
Polygon:
allOf:
- $ref: '#/components/schemas/GeoJsonObject'
- type: object
properties:
coordinates:
type: array
items:
type: number
format: double
MultiPolygon:
allOf:
- $ref: '#/components/schemas/GeoJsonObject'
- type: object
properties:
coordinates:
type: array
items:
type: array
items:
type: number
format: double
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
openapi: 3.0.3
info:
title: GeoJSON Discriminator Test
version: 1.0.0
paths: {}
components:
schemas:
GeoJsonObject:
type: object
properties:
type:
type: string
required:
- type
discriminator:
propertyName: type
mapping:
Polygon: '#/components/schemas/Polygon'
MultiPolygon: '#/components/schemas/MultiPolygon'
oneOf:
- $ref: '#/components/schemas/Polygon'
- $ref: '#/components/schemas/MultiPolygon'
Polygon:
allOf:
- $ref: '#/components/schemas/GeoJsonObject'
- type: object
properties:
coordinates:
type: array
items:
type: number
format: double
MultiPolygon:
allOf:
- $ref: '#/components/schemas/GeoJsonObject'
- type: object
properties:
coordinates:
type: array
items:
type: array
items:
type: number
format: double
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
openapi: 3.0.3
info:
title: GeoJSON Discriminator Test
version: 1.0.0
paths: {}
components:
schemas:
GeoJsonObject:
type: object
properties:
type:
type: string
required:
- type
discriminator:
propertyName: type
mapping:
Polygon: '#/components/schemas/Polygon'
MultiPolygon: '#/components/schemas/MultiPolygon'
Polygon:
allOf:
- $ref: '#/components/schemas/GeoJsonObject'
- type: object
properties:
coordinates:
type: array
items:
type: number
format: double
MultiPolygon:
allOf:
- $ref: '#/components/schemas/GeoJsonObject'
- type: object
properties:
coordinates:
type: array
items:
type: array
items:
type: number
format: double
11 changes: 9 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
<developerConnection>scm:git:git@github.com:openapitools/openapi-generator.git</developerConnection>
<url>https://github.com/openapitools/openapi-generator</url>
</scm>
<modules>
<module>modules/openapi-generator-core</module>
<module>modules/openapi-generator</module>
<module>modules/openapi-generator-cli</module>
<module>modules/openapi-generator-maven-plugin</module>
<!-- <module>modules/openapi-generator-gradle-plugin</module>-->
<!-- <module>modules/openapi-generator-mill-plugin</module>-->
<!-- <module>modules/openapi-generator-online</module>-->
</modules>
<developers>
<!-- original author of the project -->
<developer>
Expand Down Expand Up @@ -408,7 +417,6 @@
<plugin>
<groupId>com.gradle</groupId>
<artifactId>develocity-maven-extension</artifactId>
<version>${develocity-maven-extension.version}</version>
<configuration>
<develocity>
<normalization>
Expand Down Expand Up @@ -1235,7 +1243,6 @@
<commons-io.version>2.20.0</commons-io.version>
<commons-lang.version>3.18.0</commons-lang.version>
<commons-text.version>1.10.0</commons-text.version>
<develocity-maven-extension.version>1.23.2</develocity-maven-extension.version>
<diffutils.version>1.3.0</diffutils.version>
<generex.version>1.0.2</generex.version>
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
Expand Down
Loading