Skip to content
Closed
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 @@ -267,6 +267,8 @@ apiTemplateFiles are for API outputs only (controllers/handlers).
// Support legacy logic for evaluating discriminators
protected boolean legacyDiscriminatorBehavior = true;

private boolean uniqueModelMappings = false;

// Specify what to do if the 'additionalProperties' keyword is not present in a schema.
// See CodegenConstants.java for more details.
protected boolean disallowAdditionalPropertiesIfNotPresent = true;
Expand Down Expand Up @@ -1365,6 +1367,14 @@ public void setLegacyDiscriminatorBehavior(boolean val) {
this.legacyDiscriminatorBehavior = val;
}

public boolean isUniqueModelMappings() {
return uniqueModelMappings;
}

public void setUniqueModelMappings(boolean uniqueModelMappings) {
this.uniqueModelMappings = uniqueModelMappings;
}

public Boolean getDisallowAdditionalPropertiesIfNotPresent() {
return disallowAdditionalPropertiesIfNotPresent;
}
Expand Down Expand Up @@ -3361,13 +3371,19 @@ protected List<MappedModel> getAllOfDescendants(String thisSchemaName, OpenAPI o
break;
}
currentSchemaName = queue.remove(0);
MappedModel mm = new MappedModel(currentSchemaName, toModelName(currentSchemaName));
descendentSchemas.add(mm);
Schema cs = schemas.get(currentSchemaName);
Map<String, Object> vendorExtensions = cs.getExtensions();
if (vendorExtensions != null && !vendorExtensions.isEmpty() && vendorExtensions.containsKey("x-discriminator-value")) {
boolean hasDiscriminatorValue =
vendorExtensions != null && !vendorExtensions.isEmpty()
&& vendorExtensions.containsKey("x-discriminator-value");

if (!uniqueModelMappings || !hasDiscriminatorValue) {
MappedModel mm = new MappedModel(currentSchemaName, toModelName(currentSchemaName));
descendentSchemas.add(mm);
}
if (hasDiscriminatorValue) {
String xDiscriminatorValue = (String) vendorExtensions.get("x-discriminator-value");
mm = new MappedModel(xDiscriminatorValue, toModelName(currentSchemaName));
MappedModel mm = new MappedModel(xDiscriminatorValue, toModelName(currentSchemaName));
descendentSchemas.add(mm);
}
}
Expand Down Expand Up @@ -3422,7 +3438,8 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
// add only if the mapping names are not the same
boolean matched = false;
for (MappedModel uniqueDescendant : uniqueDescendants) {
if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName())) {
if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName())
|| (uniqueModelMappings && uniqueDescendant.getModelName().equals(otherDescendant.getModelName()))) {
matched = true;
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,26 @@ public void testDiscriminatorWithCustomMapping() {
Assert.assertEquals(personModel.getHasDiscriminatorWithNonEmptyMapping(), true);
}

@Test
public void testUniqueMappingDiscriminatorWithCustomMapping() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.setLegacyDiscriminatorBehavior(false);
codegen.setOpenAPI(openAPI);
codegen.setUniqueModelMappings(true);

String path = "/person/display/{personId}";
Operation operation = openAPI.getPaths().get(path).getGet();
CodegenOperation codegenOperation = codegen.fromOperation(path, "GET", operation, null);
verifyPersonDiscriminatorWithUniqueMappings(codegenOperation.discriminator);

Schema person = openAPI.getComponents().getSchemas().get("Person");
codegen.setOpenAPI(openAPI);
CodegenModel personModel = codegen.fromModel("Person", person);
verifyPersonDiscriminatorWithUniqueMappings(personModel.discriminator);
Assert.assertEquals(personModel.getHasDiscriminatorWithNonEmptyMapping(), true);
}

@Test
public void testParentName() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf.yaml");
Expand Down Expand Up @@ -1592,6 +1612,54 @@ public void verifyXDiscriminatorValue() {
assertEquals(cm.discriminator, discriminator);
}

@Test
public void verifyXDiscriminatorValueWithUniqueMappingNames() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/x-discriminator-value.yaml");
final DefaultCodegen config = new DefaultCodegen();
config.setOpenAPI(openAPI);
config.setUniqueModelMappings(true);

String modelName;
CodegenDiscriminator discriminator;
CodegenModel cm;

Boolean dryRun = Boolean.TRUE;
final DefaultGenerator generator = new DefaultGenerator(dryRun);
generator.openAPI = openAPI;
generator.config = config;
generator.configureGeneratorProperties();

// for us to check a model's children we need to run generator.generateModels
// because children are assigned in config.updateAllModels which is invoked in generator.generateModels
List<File> files = new ArrayList<>();
List<String> filteredSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI);
List<ModelMap> allModels = new ArrayList<>();
generator.generateModels(files, allModels, filteredSchemas);

// check that the model's children contain the x-discriminator-values
modelName = "BaseObj";
cm = getModel(allModels, modelName);
Assert.assertNotNull(cm);
Assert.assertNotNull(cm.children);
List<String> expectedDiscriminatorValues = new ArrayList<>(Arrays.asList("daily", "sub-obj"));
ArrayList<String> xDiscriminatorValues = new ArrayList<>();
for (CodegenModel child : cm.children) {
xDiscriminatorValues.add((String) child.vendorExtensions.get("x-discriminator-value"));
}
assertEquals(xDiscriminatorValues, expectedDiscriminatorValues);

// check that the discriminator's MappedModels also contains the x-discriminator-values
discriminator = new CodegenDiscriminator();
String prop = "object_type";
discriminator.setPropertyName(config.toVarName(prop));
discriminator.setPropertyBaseName(prop);
discriminator.setMapping(null);
discriminator.setMappedModels(new HashSet<CodegenDiscriminator.MappedModel>() {{
add(new CodegenDiscriminator.MappedModel("daily", "DailySubObj"));
add(new CodegenDiscriminator.MappedModel("sub-obj", "SubObj"));
}});
assertEquals(cm.discriminator, discriminator);
}

@Test
public void testAllOfSingleRefNoOwnProps() {
Expand Down Expand Up @@ -1996,6 +2064,18 @@ private void verifyPersonDiscriminator(CodegenDiscriminator discriminator) {
Assert.assertEquals(discriminator, test);
}

private void verifyPersonDiscriminatorWithUniqueMappings(CodegenDiscriminator discriminator) {
CodegenDiscriminator test = new CodegenDiscriminator();
test.setPropertyName("DollarUnderscoretype");
test.setPropertyBaseName("$_type");
test.setMapping(new HashMap<>());
test.getMapping().put("a", "#/components/schemas/Adult");
test.getMapping().put("c", "Child");
test.getMappedModels().add(new CodegenDiscriminator.MappedModel("a", "Adult"));
test.getMappedModels().add(new CodegenDiscriminator.MappedModel("c", "Child"));
Assert.assertEquals(discriminator, test);
}

private CodegenProperty codegenPropertyWithArrayOfIntegerValues() {
CodegenProperty array = new CodegenProperty();
final CodegenProperty items = new CodegenProperty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,45 @@ public void testOneOfAndAllOf() throws IOException {
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal");
}

@Test
public void testMappingSubtypesIssue13150() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');
OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/bugs/issue_13150.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.setUseOneOfInterfaces(true);
codegen.setUniqueModelMappings(true);

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

DefaultGenerator generator = new DefaultGenerator();
codegen.setHateoas(true);
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false");

codegen.setUseOneOfInterfaces(true);
codegen.setLegacyDiscriminatorBehavior(false);

generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");

generator.opts(input).generate();

String jsonSubType = "@JsonSubTypes({\n" +
" @JsonSubTypes.Type(value = Foo.class, name = \"foo\")\n" +
"})";
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Parent.java"), jsonSubType);
}

@Test
public void testTypeMappings() {
final SpringCodegen codegen = new SpringCodegen();
Expand Down
60 changes: 60 additions & 0 deletions modules/openapi-generator/src/test/resources/bugs/issue_13150.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
openapi: '3.0.0'
info:
version: '1.0.0'
title: 'FooService'
paths:
/parent:
put:
summary: put parent
operationId: putParent
parameters:
- name: name
in: path
required: true
description: Name of the account being updated.
schema:
type: string
requestBody:
description: The updated account definition to save.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Parent'
responses:
'200':
$ref: '#/components/responses/Parent'
components:
schemas:
Parent:
type: object
description: Defines an account by name.
properties:
name:
type: string
description: The account name.
type:
type: string
description: The account type discriminator.
required:
- name
- type
discriminator:
propertyName: type
mapping:
foo: '#/components/schemas/Foo'

Foo:
allOf:
- $ref: "#/components/schemas/Parent"
- type: object
properties:
fooType:
type: string
responses:
Parent:
description: The saved account definition.
content:
application/json:
schema:
$ref: '#/components/schemas/Parent'