Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
59c67f2
Merge pull request #4 from OpenAPITools/master
jpfinne May 11, 2026
784da69
Merge branch 'OpenAPITools:master' into master
jpfinne May 12, 2026
9ee6075
Merge branch 'OpenAPITools:master' into master
jpfinne May 14, 2026
91ce7ba
Merge branch 'OpenAPITools:master' into master
jpfinne May 15, 2026
0808d0e
Merge branch 'OpenAPITools:master' into master
jpfinne May 17, 2026
46d9c9c
Merge branch 'OpenAPITools:master' into master
jpfinne May 18, 2026
7b6cf90
Merge branch 'OpenAPITools:master' into master
jpfinne May 30, 2026
b50a337
Merge branch 'OpenAPITools:master' into master
jpfinne Jun 1, 2026
ae1bff7
Merge branch 'OpenAPITools:master' into master
jpfinne Jun 2, 2026
5229d8d
Merge branch 'OpenAPITools:master' into master
jpfinne Jun 3, 2026
cdc74eb
Merge branch 'OpenAPITools:master' into master
jpfinne Jun 13, 2026
df30bd2
Merge branch 'OpenAPITools:master' into master
jpfinne Jul 4, 2026
246b21d
Merge branch 'OpenAPITools:master' into master
jpfinne Jul 8, 2026
8e39563
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 1, 2026
deec130
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 4, 2026
da9270c
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 8, 2026
7e4ce22
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 8, 2026
4f55095
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 9, 2026
a614eb2
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 10, 2026
e8f0a38
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 13, 2026
8199a48
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 18, 2026
4757c1f
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 19, 2026
9e1e82b
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 24, 2026
f89dba7
Merge branch 'OpenAPITools:master' into master
jpfinne Aug 30, 2026
79f698b
issue_24769 sample
jpfinne Aug 30, 2026
e0e2b81
Fix #24769: improve common discriminator getter
jpfinne Aug 30, 2026
460bbd7
Fix #24769: common string schema with no format
jpfinne Aug 30, 2026
b87497e
workaround for some misconfigured unit tests
jpfinne Aug 31, 2026
76578db
Fallback to default behaviour for Kotlin
jpfinne Aug 31, 2026
9295e12
Fallback to default behaviour for Kotlin
jpfinne Aug 31, 2026
0e5839f
Fallback to string if not type
jpfinne Aug 31, 2026
97fcea6
Hanling of $ref to find discriminatorType
jpfinne Aug 31, 2026
e5c34f7
cleanup
jpfinne Aug 31, 2026
4a596b6
Remove not supported by JavaFileAssert test on sealed interface
jpfinne Aug 31, 2026
1327168
Update modules/openapi-generator/src/main/java/org/openapitools/codeg…
jpfinne Aug 31, 2026
45c00b4
Update modules/openapi-generator/src/main/java/org/openapitools/codeg…
jpfinne Aug 31, 2026
faaf92f
Update modules/openapi-generator/src/main/java/org/openapitools/codeg…
jpfinne Aug 31, 2026
9d5de48
Minor cleanup after code review
jpfinne Sep 1, 2026
9b2bf73
Some variation of #19194
jpfinne Sep 1, 2026
55a3b06
Fix typo
jpfinne Sep 1, 2026
c17db5d
More tests
jpfinne Sep 2, 2026
27842a2
Fix string not mapped
jpfinne Sep 2, 2026
9a28494
Use getPrimitiveType()
jpfinne Sep 3, 2026
97909e8
Enum improvement
jpfinne Sep 3, 2026
74593ab
Test more cases
jpfinne Sep 5, 2026
247125e
Avoid NPE
jpfinne Sep 5, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -3869,9 +3869,93 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
* @param discriminatorPropertyName The name of the discriminator property.
*/
protected String getDiscriminatorPropertyType(Schema schema, String discriminatorPropertyName) {
return DiscriminatorUtils.getDiscriminatorPropertyType(schema, discriminatorPropertyName)
String type = DiscriminatorUtils.getDiscriminatorPropertyType(schema, discriminatorPropertyName)
.map(this::toModelName)
.orElseGet(() -> typeMapping.get("string"));
.orElse(null);
if (type != null) {
return type;
}
List<Schema> schemas = DiscriminatorUtils.getDistinctTypes(openAPI, schema, discriminatorPropertyName);
return getCommonSchemaType(schemas);
}

/**
* Get the most commons denominator schemaType for several schemas.
* <p>
* @param schemas the list of schemas to compare.
*
* @Return the comman type
*/
protected String getCommonSchemaType(List<Schema> schemas) {
switch (schemas.size()) {
case 0:
//. keep string for backward compatibility
return typeMapping.get("string");
case 1:

Schema first = schemas.get(0);
try {
if (StringUtils.isNotEmpty(first.get$ref())) {
return toModelName(ModelUtils.getSimpleRef(first.get$ref()));
}
if (ModelUtils.isEnumSchema(first)) {
// inline enum, use the least common denominator
String simpleType = typeMapping.get("enum");

return simpleType != null? simpleType: typeMapping.get("object");
}
return typeMapping.get(getPrimitiveType(first));
} catch (Exception e) {
// fallback for some unit test misconfigurations...
LOGGER.warn("Unable to model name for " + first, e);
return typeMapping.get("string");
}
default:
break;
}

// boolean allRef = schemas.stream().allMatch(s -> s.get$ref() != null);
// if (allRef) {
// Set<String> modelNames = schemas.stream()
// .map(s -> toModelName(ModelUtils.getSimpleRef(s.get$ref())))
// .filter(Objects::nonNull)
// .collect(Collectors.toSet());
// if (modelNames.size() == 1) {
// return modelNames.iterator().next();
// }
// }
schemas = schemas.stream().map(s -> ModelUtils.getReferencedSchema(openAPI, s)).collect(Collectors.toList());
return getCommonTypeMapping(schemas);
}

/**
* Find the common type between different schemas.
*
* @param schemas list of more than one schema.
* @return the common matching mapped type
*/
protected String getCommonTypeMapping(List<Schema> schemas) {
String simpleType = "object";

boolean allEnums = schemas.stream().allMatch(ModelUtils::isEnumSchema);
if (allEnums) {
// non matching enums. Use enum if if the langage can map it.
if (typeMapping.containsKey("enum")) {
simpleType = "enum";
}
return typeMapping.get(simpleType);
}
if (schemas.stream().noneMatch(ModelUtils::isEnumSchema)) {
Set<String> types = schemas.stream().map(this::getPrimitiveType).collect(Collectors.toSet());
if (types.size() == 1) {
String foundPrimitiveType = types.iterator().next();
if (typeMapping.containsKey(foundPrimitiveType)) {
// matching simple type
simpleType = foundPrimitiveType;
}
}
}
return typeMapping.get(simpleType);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ public AbstractJavaCodegen() {
typeMapping.put("date", "Date");
typeMapping.put("file", "File");
typeMapping.put("AnyType", "Object");
typeMapping.put("enum", "Enum");

importMapping.put("BigDecimal", "java.math.BigDecimal");
importMapping.put("UUID", "java.util.UUID");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,16 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
return objs;
}

/**
* Kotlin has its own implementation in postProcessAllModels
*
* @return
*/
@Override
protected String getCommonSchemaType(List<Schema> schemas) {
return typeMapping.get("string");
}

@Override
public ModelsMap postProcessModels(ModelsMap objs) {
objs = super.postProcessModelsEnum(objs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.slf4j.helpers.MessageFormatter;

import java.util.*;
import java.util.stream.Collectors;

import static org.openapitools.codegen.CodegenConstants.X_DISCRIMINATOR_VALUE;
import static org.openapitools.codegen.utils.OnceLogger.once;
Expand Down Expand Up @@ -322,6 +323,130 @@ private static CodegenProperty getDiscriminatorCodegenProperty(OpenAPI openAPI,
return null;
}

/**
* Get the best matching simple types for all the mapped schemas
* @param openAPI The openAPI specification
* @param schema The Schema that may contain the discriminator
* @param discPropName The String that is the discriminator propertyName in the schema
* @return the distinct list of property Schema with the requested discPropName
*/
public static List<Schema> getDistinctTypes(OpenAPI openAPI, Schema schema, String discPropName) {
List<Schema> mappedSchemas = getMappedSchemas(openAPI, schema);
List<Schema> properties = new ArrayList<>();
for (Schema s: mappedSchemas) {
Schema prop = findProperty(openAPI, s, discPropName, new HashSet<>());
if (prop != null && properties.stream().noneMatch(p ->
Objects.equals(p.getType(), prop.getType()) &&
Objects.equals(p.get$ref(), prop.get$ref()) &&
Objects.equals(p.getEnum(), prop.getEnum()) &&
Objects.equals(p.getFormat(), prop.getFormat()))) {
properties.add(prop);
}
}
return properties;
}

/**
* return the list of dereferenced mapping schemas.
* @return the schemas found or empty list if not found.
*/
public static List<Schema> getMappedSchemas(OpenAPI openAPI, Schema schema) {
if (schema.getDiscriminator() != null) {
if (schema.getDiscriminator().getMapping() != null) {
return schema.getDiscriminator().getMapping().values().stream()
.map(ref -> getReferencedSchema(openAPI, ref))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
if (schema.getOneOf() != null && !schema.getOneOf().isEmpty()) {
// try also oneOf without discriminator mapping
List<Schema> oneOfs = (List<Schema>) schema.getOneOf();
return oneOfs.stream()
.filter(Objects::nonNull)
.map(oneOf -> oneOf.get$ref()!=null
? getReferencedSchema(openAPI, oneOf.get$ref())
: oneOf)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
return Collections.emptyList();
}

private static Schema getReferencedSchema(OpenAPI openAPI, String ref) {
if (ref == null) {
return null;
}
if (ref.indexOf('/') >= 0) {
ref = ModelUtils.getSimpleRef(ref);
}
return ModelUtils.getSchema(openAPI, ref);
}

/**
* Recursively try to find the schema matching the propertyName.
*
* @return the schema found or null if not found
*/
public static Schema findProperty(OpenAPI openAPI, Schema schema, String propertyName, Set<Schema> visitedSchemas) {
schema = ModelUtils.getReferencedSchema(openAPI, schema);
if (propertyName == null || schema == null || visitedSchemas.contains(schema)) {
return null;
}
visitedSchemas.add(schema);
Map<String, Schema> properties = schema.getProperties();
if (properties != null) {
Schema property = properties.get(propertyName);
if (property != null) {
Schema found = simplifyProperty(property, visitedSchemas);
return found;
}
}
// loop into parent allOfs
List<Schema> allOfs = schema.getAllOf();
if (allOfs != null) {
for (Schema child : allOfs) {
Schema found = findProperty(openAPI, child, propertyName, visitedSchemas);
if (found != null) {
return simplifyProperty(found, visitedSchemas);
}
}
}
return null;
}

private static Schema simplifyProperty(Schema schema, Set<Schema> visitedSchemas) {
if (!ModelUtils.isAllOf(schema)) {
return schema;
}
if (visitedSchemas.contains(schema)) {
return null;
}
visitedSchemas.add(schema);

/*
* handle type+description. For Example:
* petType:
* $ref: '#/components/schemas/PetType'
* description: DOG
*/
Schema found = null;
int count = 0;
for (Schema sc: (List<Schema>)schema.getAllOf()) {
if (ModelUtils.isAnyType(sc) || ModelUtils.hasRef(sc) ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: For an allOf property represented as [$ref, {description: ...}], the description-only member satisfies isAnyType and overwrites found. count then stays zero, so simplifyProperty returns null instead of the referenced type; only typed/ref members should populate found.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/DiscriminatorUtils.java, line 440:

<comment>For an allOf property represented as `[$ref, {description: ...}]`, the description-only member satisfies `isAnyType` and overwrites `found`. `count` then stays zero, so `simplifyProperty` returns null instead of the referenced type; only typed/ref members should populate `found`.</comment>

<file context>
@@ -392,21 +402,55 @@ public static Schema findProperty(OpenAPI openAPI, Schema schema, String propert
+        Schema found = null;
+        int count = 0;
+        for (Schema sc: (List<Schema>)schema.getAllOf()) {
+            if (ModelUtils.isAnyType(sc) || ModelUtils.hasRef(sc) ) {
+                found = sc;
+            } else {
</file context>
Suggested change
if (ModelUtils.isAnyType(sc) || ModelUtils.hasRef(sc) ) {
if (ModelUtils.hasRef(sc) || ModelUtils.getType(sc) != null) {

found = sc;
} else {
count++;
}
}
if (count == schema.getAllOf().size()-1) {
return found;
}
// multiple types, too complex.
return null;

}

public static class DiscriminatorData {
private final Discriminator discriminator;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.openapitools.codegen.java.assertions;

import com.github.javaparser.ParseProblemException;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
Expand Down Expand Up @@ -36,7 +37,7 @@ public static JavaFileAssert assertThat(final String source) {
public static JavaFileAssert assertThat(final Path path) {
try {
return new JavaFileAssert(StaticJavaParser.parse(path));
} catch (IOException e) {
} catch (IOException | ParseProblemException e) {
throw new RuntimeException("Exception while reading file: " + path, e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9335,4 +9335,56 @@ public void issue_24232() throws IOException {
JavaFileAssert.assertThat(files.get("Dummy.java"))
.fileContains("import org.myorg.MyCustomId;", "import org.myorg.MyCustomKey;");
}

@Test
public void issue_24769() throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/oneOf_issue_24769.yaml", SPRING_BOOT,
Map.of(USE_SPRING_BOOT4, true)
);

JavaFileAssert.assertThat(files.get("Dog.java"))
.fileContains("public enum TypeEnum {",
"DOG(\"DOG\");");
JavaFileAssert.assertThat(files.get("Cat.java"))
.fileContains("public enum TypeEnum {",
"CAT(\"CAT\");");
JavaFileAssert.assertThat(files.get("Pet.java"))
.fileDoesNotContain("public enum TypeEnum {")
.fileContains("public Enum getType();");

JavaFileAssert.assertThat(files.get("PetInteger.java"))
.fileContains("public Integer getIntType();");

JavaFileAssert.assertThat(files.get("PetEnumRef.java"))
.fileContains("public PetEnumType getEnumRefType();");

JavaFileAssert.assertThat(files.get("PetWithParent.java"))
.fileContains("public PetEnumType getPetType();");

JavaFileAssert.assertThat(files.get("PetNoMapping.java"))
.fileContains("public Enum getType();");
}

@DataProvider(name = "oneOfDiscriminatorType")
public Object[][] oneOfDiscriminatorType() {
return new Object[][]{
{"/3_0/oneOf_issue_19194.yaml", true, "CargoInterface.java", "public CargoGeneralParameterUnit getUnit();"},
{"/3_0/oneOf_issue_19194.yaml", false, "CargoInterface.java", "public Enum getUnit();"},
{"/3_0/oneOf_issue_19194_v2.yaml", false, "CargoParent.java", "public Object getUnit()"},
{"/3_0/oneof_polymorphism_and_inheritance.yaml", false, "FooRefOrValue.java", "public String getAtType()"}
};
}

@Test(dataProvider = "oneOfDiscriminatorType")
public void oneOfDiscriminatorType(String filename, boolean resolveInlineEnum, String fileToCheck, String expectedContains) throws IOException {
Map<String, File> files = generateFromContract(
"src/test/resources" + filename, SPRING_BOOT,
Map.of(USE_SPRING_BOOT4, true), configurator->
configurator.addInlineSchemaOption("RESOLVE_INLINE_ENUMS", Boolean.toString(resolveInlineEnum))
);
JavaFileAssert.assertThat(files.get(fileToCheck))
.fileContains(expectedContains);
}

}
Loading
Loading