Skip to content
GitLab
    • Explore Projects Groups Snippets
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
  • O openapi-generator
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 3,476
    • Issues 3,476
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 402
    • Merge requests 402
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Infrastructure Registry
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • OpenAPI Tools
  • openapi-generator
  • Merge requests
  • !7106
An error occurred while fetching the assigned milestone of the selected merge_request.

[bug][core] Copy all attributes (not properties) on composed schemas when flattening models

  • Review changes

  • Download
  • Email patches
  • Plain diff
Merged Jim Schubert requested to merge jersey-inheritance into master 4 years ago
  • Overview 0
  • Commits 6
  • Pipelines 0
  • Changes 4

Fixes #6950 (closed)

The model flattening for composed schemas was not copying all attributes of the schema being flattened. This broke inheritance across composed schemas, which is a feature that is supported by the specification.

cc @OpenAPITools/generator-core-team cc @jeff9finger

Example output showing proper inheritance using spec from #6950 (closed):

image

PR checklist

  • Read the contribution guidelines.
  • If contributing template-only or documentation-only changes which will change sample output, build the project beforehand.
  • Run the shell script ./bin/generate-samples.shto update all Petstore samples related to your fix. This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master. These must match the expectations made by your contribution. You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*. For Windows users, please run the script in Git BASH.
  • File the PR against the correct branch: master
  • Copy the technical committee to review the pull request if your PR is targeting a particular programming language.
Compare
  • master (base)

and
  • latest version
    a034b52f
    6 commits, 2 years ago

4 files
+ 344
- 13

    Preferences

    File browser
    Compare changes
modules/openap‎i-generator/src‎
main/java/org/ope‎napitools/codegen‎
InlineModelR‎esolver.java‎ +62 -13
te‎st‎
java/org/opena‎pitools/codegen‎
InlineModelRes‎olverTest.java‎ +108 -0
resour‎ces/2_0‎
regression‎_6905.yaml‎ +172 -0
samples/openap‎i3/…/…/…/…/api‎
openap‎i.yaml‎ +2 -0
modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java
+ 62
- 13
  • View file @ a034b52f

  • Edit in single-file editor

  • Open in Web IDE


@@ -17,6 +17,10 @@
package org.openapitools.codegen;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.callbacks.Callback;
@@ -25,6 +29,7 @@ import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,6 +41,17 @@ public class InlineModelResolver {
private OpenAPI openapi;
private Map<String, Schema> addedModels = new HashMap<String, Schema>();
private Map<String, String> generatedSignature = new HashMap<String, String>();
// structure mapper sorts properties alphabetically on write to ensure models are
// serialized consistently for lookup of existing models
private static ObjectMapper structureMapper;
static {
structureMapper = Json.mapper().copy();
structureMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
structureMapper.writer(new DefaultPrettyPrinter());
}
static Logger LOGGER = LoggerFactory.getLogger(InlineModelResolver.class);
void flatten(OpenAPI openapi) {
@@ -488,15 +504,25 @@ public class InlineModelResolver {
}
private String matchGenerated(Schema model) {
String json = Json.pretty(model);
if (generatedSignature.containsKey(json)) {
return generatedSignature.get(json);
try {
String json = structureMapper.writeValueAsString(model);
if (generatedSignature.containsKey(json)) {
return generatedSignature.get(json);
}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
private void addGenerated(String name, Schema model) {
generatedSignature.put(Json.pretty(model), name);
try {
String json = structureMapper.writeValueAsString(model);
generatedSignature.put(json, name);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
/**
@@ -620,22 +646,45 @@ public class InlineModelResolver {
}
XML xml = object.getXml();
Map<String, Schema> properties = object.getProperties();
// NOTE:
// No need to null check setters below. All defaults in the new'd Schema are null, so setting to null would just be a noop.
Schema model = new Schema();
if (object.getType() != null) {
model.setType(object.getType());
}
if (object.getFormat() != null) {
// Even though the `format` keyword typically applies to primitive types only,
// the JSON schema specification states `format` can be used for any model type instance
// including object types.
model.setFormat(object.getFormat());
}
model.setType(object.getType());
// Even though the `format` keyword typically applies to primitive types only,
// the JSON schema specification states `format` can be used for any model type instance
// including object types.
model.setFormat(object.getFormat());
model.setDescription(description);
model.setExample(example);
model.setName(object.getName());
model.setXml(xml);
model.setRequired(object.getRequired());
model.setNullable(object.getNullable());
model.setDiscriminator(object.getDiscriminator());
model.setWriteOnly(object.getWriteOnly());
model.setUniqueItems(object.getUniqueItems());
model.setTitle(object.getTitle());
model.setReadOnly(object.getReadOnly());
model.setPattern(object.getPattern());
model.setNot(object.getNot());
model.setMinProperties(object.getMinProperties());
model.setMinLength(object.getMinLength());
model.setMinItems(object.getMinItems());
model.setMinimum(object.getMinimum());
model.setMaxProperties(object.getMaxProperties());
model.setMaxLength(object.getMaxLength());
model.setMaxItems(object.getMaxItems());
model.setMaximum(object.getMaximum());
model.setExternalDocs(object.getExternalDocs());
model.setExtensions(object.getExtensions());
model.setExclusiveMinimum(object.getExclusiveMinimum());
model.setExclusiveMaximum(object.getExclusiveMaximum());
model.setExample(object.getExample());
model.setDeprecated(object.getDeprecated());
if (properties != null) {
flattenProperties(openAPI, properties, path);
model.setProperties(properties);
modules/openapi-generator/src/test/java/org/openapitools/codegen/InlineModelResolverTest.java
+ 108
- 0
  • View file @ a034b52f

  • Edit in single-file editor

  • Open in Web IDE


@@ -785,6 +785,109 @@ public class InlineModelResolverTest {
checkComposedChildren(openAPI, schema.getOneOf(), "oneOf");
}
@Test
public void inheritanceWithInlineDiscriminator() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/2_0/regression_6905.yaml");
new InlineModelResolver().flatten(openAPI);
assertTrue(openAPI.getComponents().getSchemas().get("PartyType") instanceof StringSchema);
assertTrue(openAPI.getComponents().getSchemas().get("CustomerType") instanceof StringSchema);
assertTrue(openAPI.getComponents().getSchemas().get("Entity") instanceof ObjectSchema);
assertTrue(openAPI.getComponents().getSchemas().get("Party") instanceof ComposedSchema);
assertTrue(openAPI.getComponents().getSchemas().get("Contact") instanceof ComposedSchema);
assertTrue(openAPI.getComponents().getSchemas().get("Customer") instanceof ComposedSchema);
assertTrue(openAPI.getComponents().getSchemas().get("Person") instanceof ComposedSchema);
assertTrue(openAPI.getComponents().getSchemas().get("Organization") instanceof ComposedSchema);
assertTrue(openAPI.getComponents().getSchemas().get("ApiError") instanceof ObjectSchema);
assertFalse(openAPI.getComponents().getSchemas().get("Party_allOf") instanceof ComposedSchema);
assertFalse(openAPI.getComponents().getSchemas().get("Contact_allOf") instanceof ComposedSchema);
assertFalse(openAPI.getComponents().getSchemas().get("Customer_allOf") instanceof ComposedSchema);
assertFalse(openAPI.getComponents().getSchemas().get("Person_allOf") instanceof ComposedSchema);
assertFalse(openAPI.getComponents().getSchemas().get("Organization_allOf") instanceof ComposedSchema);
// Party
ComposedSchema party = (ComposedSchema) openAPI.getComponents().getSchemas().get("Party");
List<Schema> partySchemas = party.getAllOf();
Schema entity = ModelUtils.getReferencedSchema(openAPI, partySchemas.get(0));
Schema partyAllOf = ModelUtils.getReferencedSchema(openAPI, partySchemas.get(1));
assertEquals(partySchemas.get(0).get$ref(), "#/components/schemas/Entity");
assertEquals(partySchemas.get(1).get$ref(), "#/components/schemas/Party_allOf");
assertNull(party.getDiscriminator());
assertNull(entity.getDiscriminator());
assertNotNull(partyAllOf.getDiscriminator());
assertEquals(partyAllOf.getDiscriminator().getPropertyName(), "party_type");
assertEquals(partyAllOf.getRequired().get(0), "party_type");
// Contact
ComposedSchema contact = (ComposedSchema) openAPI.getComponents().getSchemas().get("Contact");
Schema contactAllOf = ModelUtils.getReferencedSchema(openAPI, contact.getAllOf().get(1));
assertEquals(contact.getExtensions().get("x-discriminator-value"), "contact");
assertEquals(contact.getAllOf().get(0).get$ref(), "#/components/schemas/Party");
assertEquals(contact.getAllOf().get(1).get$ref(), "#/components/schemas/Contact_allOf");
assertNull(contactAllOf.getDiscriminator());
// Customer
ComposedSchema customer = (ComposedSchema) openAPI.getComponents().getSchemas().get("Customer");
List<Schema> customerSchemas = customer.getAllOf();
Schema customerAllOf = ModelUtils.getReferencedSchema(openAPI, customerSchemas.get(1));
assertEquals(customerSchemas.get(0).get$ref(), "#/components/schemas/Party");
assertNull(customer.getDiscriminator());
assertEquals(customer.getExtensions().get("x-discriminator-value"), "customer");
// Discriminators are not defined at this level in the schema doc
assertNull(customerSchemas.get(0).getDiscriminator());
assertEquals(customerSchemas.get(1).get$ref(), "#/components/schemas/Customer_allOf");
assertNull(customerSchemas.get(1).getDiscriminator());
// Customer -> Party where Customer defines it's own discriminator
assertNotNull(customerAllOf.getDiscriminator());
assertEquals(customerAllOf.getDiscriminator().getPropertyName(), "customer_type");
assertEquals(customerAllOf.getRequired().get(0), "customer_type");
// Person
ComposedSchema person = (ComposedSchema) openAPI.getComponents().getSchemas().get("Person");
List<Schema> personSchemas = person.getAllOf();
Schema personAllOf = ModelUtils.getReferencedSchema(openAPI, personSchemas.get(1));
// Discriminators are not defined at this level in the schema doc
assertEquals(personSchemas.get(0).get$ref(), "#/components/schemas/Customer");
assertNull(personSchemas.get(0).getDiscriminator());
assertEquals(personSchemas.get(1).get$ref(), "#/components/schemas/Person_allOf");
assertNull(personSchemas.get(1).getDiscriminator());
// Person -> Customer -> Party, so discriminator is not at this level
assertNull(person.getDiscriminator());
assertEquals(person.getExtensions().get("x-discriminator-value"), "person");
assertNull(personAllOf.getDiscriminator());
// Organization
ComposedSchema organization = (ComposedSchema) openAPI.getComponents().getSchemas().get("Organization");
List<Schema> organizationSchemas = organization.getAllOf();
Schema organizationAllOf = ModelUtils.getReferencedSchema(openAPI, organizationSchemas.get(1));
// Discriminators are not defined at this level in the schema doc
assertEquals(organizationSchemas.get(0).get$ref(), "#/components/schemas/Customer");
assertNull(organizationSchemas.get(0).getDiscriminator());
assertEquals(organizationSchemas.get(1).get$ref(), "#/components/schemas/Organization_allOf");
assertNull(organizationSchemas.get(1).getDiscriminator());
// Organization -> Customer -> Party, so discriminator is not at this level
assertNull(organizationAllOf.getDiscriminator());
assertEquals(organization.getExtensions().get("x-discriminator-value"), "organization");
}
@Test
public void arbitraryObjectModelWithArrayInlineWithTitle() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/inline_model_resolver.yaml");
@@ -888,4 +991,9 @@ public class InlineModelResolverTest {
assertTrue(properties.get("action") instanceof StringSchema);
assertTrue(properties.get("data") instanceof StringSchema);
}
@Test
public void regresssion_6905() {
}
}
\ No newline at end of file
modules/openapi-generator/src/test/resources/2_0/regression_6905.yaml 0 → 100644
+ 172
- 0
  • View file @ a034b52f

  • Edit in single-file editor

  • Open in Web IDE

swagger: '2.0'
info:
title: Test Command model generation
description: Test Command model generation
version: 1.0.0
definitions:
PartyType:
description: type
type: string
enum:
- customer
- contact
CustomerType:
description: type
type: string
enum:
- person
- organization
Entity:
type: object
properties:
id:
type: string
readOnly: true
Party:
allOf:
- $ref: '#/definitions/Entity'
- type: object
discriminator: party_type
required:
- party_type
properties:
party_type:
readOnly: true
$ref: '#/definitions/PartyType'
tax_id_number:
type: string
Contact:
x-discriminator-value: contact
allOf:
- $ref: '#/definitions/Party'
- type: object
properties:
first_name:
type: string
last_name:
type: string
suffix:
type: string
dob:
type: string
format: date
Customer:
x-discriminator-value: customer
allOf:
- $ref: '#/definitions/Party'
- type: object
discriminator: customer_type
required:
- customer_type
properties:
customer_type:
readOnly: true
$ref: '#/definitions/CustomerType'
customer_num:
type: string
external_customer_num:
type: string
Person:
x-discriminator-value: person
allOf:
- $ref: '#/definitions/Customer'
- type: object
properties:
first_name:
type: string
last_name:
type: string
Organization:
x-discriminator-value: organization
allOf:
- $ref: '#/definitions/Customer'
- type: object
required:
- organization_name
properties:
organization_name:
type: string
ApiError:
type: object
required:
- code
- message
properties:
code:
type: string
readOnly: true
message:
type: string
readOnly: true
paths:
/customers:
get:
consumes: []
operationId: queryCustomers
tags:
- Customer
summary: Get customers
responses:
200:
description: Success
schema:
type: array
items:
$ref: '#/definitions/Customer'
400:
description: Bad request.
schema:
$ref: '#/definitions/ApiError'
default:
description: Unknown error.
schema:
$ref: '#/definitions/ApiError'
/contacts:
get:
consumes: []
operationId: queryContacts
tags:
- Contact
summary: Get contact
responses:
200:
description: Success
schema:
type: array
items:
$ref: '#/definitions/Contact'
400:
description: Bad request.
schema:
$ref: '#/definitions/ApiError'
default:
description: Unknown error.
schema:
$ref: '#/definitions/ApiError'
/parties:
get:
consumes: []
responses:
200:
description: Success
schema:
type: array
items:
$ref: '#/definitions/Party'
400:
description: Bad request.
schema:
$ref: '#/definitions/ApiError'
default:
description: Unknown error.
schema:
$ref: '#/definitions/ApiError'
samples/openapi3/client/petstore/java/jersey2-java8-special-characters/api/openapi.yaml
+ 2
- 0
  • View file @ a034b52f

  • Edit in single-file editor

  • Open in Web IDE


@@ -46,8 +46,10 @@ components:
properties:
prop1:
type: string
type: object
MySchemaName___Characters_allOf:
properties:
prop2:
type: string
type: object
0 Assignees
None
Assign to
0 Reviewers
None
Request review from
Labels
0
None
0
None
    Assign labels
  • Manage project labels

Milestone
No milestone
None
None
Time tracking
No estimate or time spent
Lock merge request
Unlocked
0
0 participants
Reference:
Source branch: jersey-inheritance

Menu

Explore Projects Groups Snippets