Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@

import static org.openapitools.codegen.CodegenConstants.*;
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.DiscriminatorUtils.*;
import static org.openapitools.codegen.utils.OnceLogger.once;
import static org.openapitools.codegen.utils.StringUtils.*;

Expand Down Expand Up @@ -1720,7 +1721,11 @@ public String toArrayModelParamName(String name) {
*/
@SuppressWarnings("static-method")
public String toEnumName(CodegenProperty property) {
return StringUtils.capitalize(property.name) + "Enum";
return toEnumName(property.name);
}

public String toEnumName(String propertyName) {
return StringUtils.capitalize(propertyName) + "Enum";
}

/**
Expand Down Expand Up @@ -3453,7 +3458,7 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
* @param sc The Schema that may contain the discriminator
* @param visitedSchemas An array list of visited schemas
*/
private Discriminator recursiveGetDiscriminator(Schema sc, ArrayList<Schema> visitedSchemas) {
private DiscriminatorData recursiveGetDiscriminator(Schema sc, ArrayList<Schema> visitedSchemas) {
return DiscriminatorUtils.recursiveGetDiscriminator(openAPI, this.getLegacyDiscriminatorBehavior(), sc, visitedSchemas);
}

Expand Down Expand Up @@ -3590,7 +3595,7 @@ protected List<MappedModel> getAllOfDescendants(String thisSchemaName) {
}

protected CodegenDiscriminator createDiscriminator(String schemaName, Schema schema) {
Discriminator sourceDiscriminator = recursiveGetDiscriminator(schema, new ArrayList<Schema>());
DiscriminatorData sourceDiscriminator = recursiveGetDiscriminator(schema, new ArrayList<Schema>());
if (sourceDiscriminator == null) {
return null;
}
Expand All @@ -3607,7 +3612,25 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
// FIXME: there are other ways to define the type of the discriminator property (inline
// for example). Handling those scenarios is too complicated for me, I'm leaving it for
// the future..
discriminator.setPropertyType(getDiscriminatorPropertyType(schema, discriminatorPropertyName));
String discriminatorType;
Schema discSchema = sourceDiscriminator.getDiscriminatorSchema();
if (discSchema != null) {
if (ModelUtils.hasRef(discSchema)) {
discriminatorType = toModelName(ModelUtils.getSimpleRef(discSchema.get$ref()));
} else {
// Inline schema (inline enum, uri, ...): resolve via the same pipeline the
// concrete model classes use so the oneOf interface getter type matches.
// Keep String for typeless const to avoid resolving to Object.
if (ModelUtils.getType(discSchema) == null || discSchema.getEnum() == null) {
discriminatorType = getTypeDeclaration(discSchema);
} else {
discriminatorType = toEnumName(discriminatorPropertyName);
}
}
} else {
discriminatorType = getDiscriminatorPropertyType(schema, discriminatorPropertyName);
}
discriminator.setPropertyType(discriminatorType);

// check to see if the discriminator property is an enum string
boolean isEnum = Optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Discriminator;
import io.swagger.v3.oas.models.media.Schema;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -16,6 +17,8 @@ public class DiscriminatorUtils {

private static final String CONFLICTING_DISCRIMINATOR_NAMES =
"The alternative schemas have conflicting discriminator property names. The schemas must have the same property name, but found {}";
private static final String CONFLICTING_DISCRIMINATOR_TYPES =
"The alternative schemas have conflicting discriminator property types ({})";

/**
* Gets the simple ref name of the discriminator property type from the schema.
Expand All @@ -35,7 +38,7 @@ public static Optional<String> getDiscriminatorPropertyType(Schema schema, Strin
* An example of a sibling is an enum-ref that has its own description. This will lead to the enum being
* referenced as an allOf that in turn has a ref, rather than a regular ref directly to the enum.
*
* @param schema The input OAS schema.
* @param schema The input OAS schema that has the discriminator as a property.
* @param discriminatorName The name of the discriminator property.
*/
public static Schema getDiscriminatorSchema(Schema schema, String discriminatorName) {
Expand All @@ -57,15 +60,19 @@ public static Schema getDiscriminatorSchema(Schema schema, String discriminatorN
* @param sc The Schema that may contain the discriminator
* @param visitedSchemas an array list of visited schemas
*/
public static Discriminator recursiveGetDiscriminator(
public static DiscriminatorData recursiveGetDiscriminator(
OpenAPI openAPI,
boolean legacyDiscriminatorBehavior,
Schema sc,
ArrayList<Schema> visitedSchemas) {
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, sc);
Discriminator foundDisc = refSchema.getDiscriminator();
if (foundDisc != null) {
return foundDisc;
DiscriminatorData foundDisc = new DiscriminatorData(refSchema.getDiscriminator(), null);
if (foundDisc.getDiscriminator() != null) {
String discriminatorPropertyName = foundDisc.getDiscriminator().getPropertyName();
return new DiscriminatorData(
foundDisc.getDiscriminator(),
DiscriminatorUtils.getDiscriminatorSchema(refSchema, discriminatorPropertyName)
);
}

if (legacyDiscriminatorBehavior) {
Expand All @@ -89,7 +96,7 @@ public static Discriminator recursiveGetDiscriminator(
if (foundDisc != null) {
disc.setPropertyName(foundDisc.getPropertyName());
disc.setMapping(foundDisc.getMapping());
return disc;
return new DiscriminatorData(disc, foundDisc.getDiscriminatorSchema());
}
}
}
Expand All @@ -115,16 +122,17 @@ public static Discriminator recursiveGetDiscriminator(
* @param visitedSchemas an array list of visited schemas
* @return the discriminator if the alternatives correctly shares one, otherwise null
*/
private static Discriminator getDiscriminatorFromAlternatives(
private static DiscriminatorData getDiscriminatorFromAlternatives(
OpenAPI openAPI,
boolean legacyDiscriminatorBehavior,
List<Schema> alternativeSchemas,
ArrayList<Schema> visitedSchemas) {
Discriminator discriminator = new Discriminator();
Discriminator foundDisc = null;
DiscriminatorData foundDisc = null;
Integer hasDiscriminatorCnt = 0;
Integer hasNullTypeCnt = 0;
Set<String> discriminatorsPropNames = new HashSet<>();
Set<Schema> discriminatorTypes = new HashSet<>();
for (Object alternative : alternativeSchemas) {
if (ModelUtils.isNullType((Schema) alternative)) {
// The null type does not have a discriminator. Skip.
Expand All @@ -135,20 +143,59 @@ private static Discriminator getDiscriminatorFromAlternatives(
if (foundDisc != null) {
discriminatorsPropNames.add(foundDisc.getPropertyName());
hasDiscriminatorCnt++;
if (foundDisc.getDiscriminatorSchema() != null) {
discriminatorTypes.add(foundDisc.getDiscriminatorSchema());
}
}
}
if (discriminatorsPropNames.size() > 1) {
once(LOGGER).warn(CONFLICTING_DISCRIMINATOR_NAMES, String.join(", ", discriminatorsPropNames));
}
if (discriminatorTypes.size() > 1) {
once(LOGGER).warn(CONFLICTING_DISCRIMINATOR_TYPES, String.join(", ", discriminatorTypes.toString()));
}
boolean allAlternativesHaveADiscriminator = hasDiscriminatorCnt + hasNullTypeCnt == alternativeSchemas.size();
if (foundDisc != null && allAlternativesHaveADiscriminator && discriminatorsPropNames.size() == 1) {
discriminator.setPropertyName(foundDisc.getPropertyName());
discriminator.setMapping(foundDisc.getMapping());
return discriminator;
Schema uniqueDiscriminatorType = discriminatorTypes.size() == 1 ? discriminatorTypes.iterator().next() : null;
return new DiscriminatorData(discriminator, uniqueDiscriminatorType);
}
// If the scenario when composite schema has two children and one of them is the 'null' type,
// there is no need for a discriminator.
return null;
}

public static class DiscriminatorData {
private final Discriminator discriminator;

@Nullable
private final Schema discriminatorSchema;

public DiscriminatorData(Discriminator discriminator, @Nullable Schema discriminatorSchema) {
this.discriminator = discriminator;
this.discriminatorSchema = discriminatorSchema;
}

public Discriminator getDiscriminator() {
return discriminator;
}

public @Nullable Schema getDiscriminatorSchema() {
return discriminatorSchema;
}

public String getPropertyName() {
return discriminator.getPropertyName();
}

public Map<String, String> getMapping() {
return discriminator.getMapping();
}

public Map<String, Object> getExtensions() {
return discriminator.getExtensions();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2392,6 +2392,16 @@ public static boolean hasAnyOf(Schema schema) {
return false;
}

/**
* Returns true if the schema contains a $ref
*
* @param schema the schema
* @return true if $ref is set
*/
public static boolean hasRef(Schema schema) {
return schema != null && schema.get$ref() != null;
}

/**
* Returns schema type.
* For 3.1 spec, return the first one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@
* This class holds data to add to `oneOf` members. Let's consider this example:
* <p>
* Foo:
* properties:
* x:
* oneOf:
* - $ref: "#/components/schemas/One
* - $ref: "#/components/schemas/Two
* y:
* type: string
* properties:
* x:
* oneOf:
* - $ref: "#/components/schemas/One"
* - $ref: "#/components/schemas/Two"
* y:
* type: string
* One:
* properties:
* z:
* type: string
* properties:
* z:
* type: string
* Two:
* properties:
* a:
* type: string
* properties:
* a:
* type: string
* <p>
* In codegens that use this mechanism, `Foo` will become an interface and `One` will
* become its implementing class. This class carries all data necessary to properly modify
Expand Down Expand Up @@ -99,7 +99,7 @@ public void addFromInterfaceModel(CodegenModel cm, List<Map<String, String>> mod
* @param cc CodegenConfig running this operation
* @param implcm the implementing model
* @param implImports imports of the implementing model
* @param addInterfaceImports whether or not to add the interface model as import (will vary by language)
* @param addInterfaceImports whether to add the interface model as import (will vary by language)
*/
@SuppressWarnings("unchecked")
public void addToImplementor(CodegenConfig cc, CodegenModel implcm, List<Map<String, String>> implImports, boolean addInterfaceImports) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,87 @@ public void testEnumDiscriminatorWithDescriptionOverridden3_1() {
assertTrue(fruitModel.getVars().get(0).isEnumRef);
}

@Test
public void testOneOfAllOfEnumRefDiscriminatorInheritance() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/oneOfDiscriminator.yaml");
new OpenAPINormalizer(openAPI, Map.of()).normalize();
DefaultCodegen codegen = new DefaultCodegen();
codegen.setUseOneOfInterfaces(true);
codegen.setLegacyDiscriminatorBehavior(false);

Schema inner = openAPI.getComponents().getSchemas().get("VehicleResponse");
codegen.setOpenAPI(openAPI);
CodegenModel innerModel = codegen.fromModel("VehicleResponse", inner);
assertTrue(innerModel.getHasDiscriminatorWithNonEmptyMapping());
assertTrue(innerModel.discriminator.getIsEnum());
assertEquals("VehicleType", innerModel.discriminator.getPropertyType());
assertTrue(innerModel.getVars().get(0).isEnumRef);

Schema car = openAPI.getComponents().getSchemas().get("Car");
CodegenModel carModel = codegen.fromModel("Car", car);
assertTrue(carModel.discriminator.getIsEnum());
assertEquals("VehicleType", carModel.discriminator.getPropertyType());
assertTrue(carModel.getVars().get(0).isEnumRef);

Schema bike = openAPI.getComponents().getSchemas().get("Bike");
CodegenModel bikeModel = codegen.fromModel("Bike", bike);
assertTrue(bikeModel.discriminator.getIsEnum());
assertEquals("VehicleType", bikeModel.discriminator.getPropertyType());
assertTrue(bikeModel.getVars().get(0).isEnumRef);
}

@Test
public void testOneOfAllOfInlineEnumDiscriminatorInheritance() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/oneOfDiscriminator.yaml");
new OpenAPINormalizer(openAPI, Map.of()).normalize();
DefaultCodegen codegen = new DefaultCodegen();
codegen.setUseOneOfInterfaces(true);
codegen.setLegacyDiscriminatorBehavior(false);

Schema inner = openAPI.getComponents().getSchemas().get("PetResponseEnumDisc");
codegen.setOpenAPI(openAPI);
CodegenModel innerModel = codegen.fromModel("PetResponseEnumDisc", inner);
assertTrue(innerModel.getHasDiscriminatorWithNonEmptyMapping());
assertTrue(innerModel.discriminator.getIsEnum());
assertEquals("PetTypeEnum", innerModel.discriminator.getPropertyType());
assertFalse(innerModel.getVars().get(0).isEnumRef);

Schema dog = openAPI.getComponents().getSchemas().get("DogEnumDisc");
CodegenModel dogModel = codegen.fromModel("DogEnumDisc", dog);
assertTrue(dogModel.discriminator.getIsEnum());
assertEquals("PetTypeEnum", dogModel.discriminator.getPropertyType());
assertFalse(dogModel.getVars().get(0).isEnumRef);

Schema cat = openAPI.getComponents().getSchemas().get("CatEnumDisc");
CodegenModel catModel = codegen.fromModel("CatEnumDisc", cat);
assertTrue(catModel.discriminator.getIsEnum());
assertEquals("PetTypeEnum", catModel.discriminator.getPropertyType());
assertFalse(catModel.getVars().get(0).isEnumRef);
}

@Test
public void testOneOfAllOfInlineStringWithFormatDiscriminatorInheritance() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/oneOfDiscriminator.yaml");
new OpenAPINormalizer(openAPI, Map.of()).normalize();
DefaultCodegen codegen = new DefaultCodegen();
codegen.setUseOneOfInterfaces(true);
codegen.setLegacyDiscriminatorBehavior(false);

Schema inner = openAPI.getComponents().getSchemas().get("PetResponseUriDisc");
codegen.setOpenAPI(openAPI);
CodegenModel innerModel = codegen.fromModel("PetResponseUriDisc", inner);
assertTrue(innerModel.getHasDiscriminatorWithNonEmptyMapping());
assertEquals("URI", innerModel.discriminator.getPropertyType());

Schema dog = openAPI.getComponents().getSchemas().get("DogUriDisc");
CodegenModel dogModel = codegen.fromModel("DogUriDisc", dog);
assertEquals("URI", dogModel.discriminator.getPropertyType());

Schema cat = openAPI.getComponents().getSchemas().get("CatUriDisc");
CodegenModel catModel = codegen.fromModel("CatUriDisc", cat);
assertEquals("URI", catModel.discriminator.getPropertyType());
}

@Test
public void testParentName() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf.yaml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,37 @@ public void testDiscriminatorWithMappingIssue14731() {
.content().doesNotContain("@JsonTypeName");
}

@Test
public void testOneOfInterfaceInheritedEnumDiscriminator() {
final Path output = newTempFolder();
final OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/oneOfDiscriminator.yaml", null, new ParseOptions())
.getOpenAPI();

// resttemplate (unlike the default okhttp-gson) renders oneOf as interfaces, so it uses the
// base Java/oneof_interface template that emits the discriminator getter type.
final JavaClientCodegen codegen = new JavaClientCodegen();
codegen.setLibrary("resttemplate");
codegen.setOutputDir(output.toString());
codegen.setUseOneOfInterfaces(true);
codegen.setLegacyDiscriminatorBehavior(false);

final ClientOptInput input = new ClientOptInput().openAPI(openAPI).config(codegen);

DefaultGenerator generator = new DefaultGenerator();
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
generator.opts(input).generate();

// Issue #22541: the inline-enum discriminator inherited from a base schema via allOf must
// resolve to the enum type in the oneOf interface getter, not String.
assertThat(output.resolve("src/main/java/org/openapitools/client/model/PetResponseEnumDisc.java"))
.content().contains("public PetTypeEnum getPetType();");
}

@Test
public void testDiscriminatorWithoutMappingIssue14731() {
final Path output = newTempFolder();
Expand Down
Loading
Loading