forked from loafle/openapi-generator-original
[bug][core] Copy all attributes (not properties) on composed schemas when flattening models (#7106)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
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'
|
||||
@@ -46,8 +46,10 @@ components:
|
||||
properties:
|
||||
prop1:
|
||||
type: string
|
||||
type: object
|
||||
MySchemaName___Characters_allOf:
|
||||
properties:
|
||||
prop2:
|
||||
type: string
|
||||
type: object
|
||||
|
||||
|
||||
Reference in New Issue
Block a user