Sets model.additionalProperties for composed schemas (#9033)

* Turns on setting model.additionalProperties for composed schemas when supportsAdditionalPropertiesWithComposedSchema is true, tests updated for v2 and v3 specs

* Comment change

* Samples updated

* Turns on supportsAdditionalPropertiesWithComposedSchema for CsharpNetCoreClient, samples regenerated

* Changes commented out, samples regnerated

* Refactors the update in setAddProps to not impact the charp client
This commit is contained in:
Justin Black 2021-03-21 19:22:18 -07:00 committed by GitHub
parent e0021e662f
commit 113d38eb71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 234 additions and 55 deletions

View File

@ -2615,35 +2615,47 @@ public class DefaultCodegen implements CodegenConfig {
}
private void setAddProps(Schema schema, IJsonSchemaValidationProperties property){
if (schema.equals(new Schema())) {
// if we are trying to set additionalProperties on an empty schema stop recursing
return;
}
boolean additionalPropertiesIsAnyType = false;
CodegenModel m = null;
if (property instanceof CodegenModel) {
m = (CodegenModel) property;
}
CodegenProperty addPropProp = null;
boolean isAdditionalPropertiesTrue = false;
if (schema.getAdditionalProperties() == null) {
if (!disallowAdditionalPropertiesIfNotPresent) {
isAdditionalPropertiesTrue = true;
CodegenProperty cp = fromProperty("", new Schema());
property.setAdditionalProperties(cp);
property.setAdditionalPropertiesIsAnyType(true);
addPropProp = fromProperty("", new Schema());
additionalPropertiesIsAnyType = true;
}
} else if (schema.getAdditionalProperties() instanceof Boolean) {
if (Boolean.TRUE.equals(schema.getAdditionalProperties())) {
isAdditionalPropertiesTrue = true;
CodegenProperty cp = fromProperty("", new Schema());
property.setAdditionalProperties(cp);
property.setAdditionalPropertiesIsAnyType(true);
addPropProp = fromProperty("", new Schema());
additionalPropertiesIsAnyType = true;
}
} else {
CodegenProperty cp = fromProperty("", (Schema) schema.getAdditionalProperties());
property.setAdditionalProperties(cp);
addPropProp = fromProperty("", (Schema) schema.getAdditionalProperties());
if (isAnyTypeSchema((Schema) schema.getAdditionalProperties())) {
property.setAdditionalPropertiesIsAnyType(true);
additionalPropertiesIsAnyType = true;
}
}
if (additionalPropertiesIsAnyType) {
property.setAdditionalPropertiesIsAnyType(true);
}
if (m != null && isAdditionalPropertiesTrue) {
m.isAdditionalPropertiesTrue = true;
}
if (ModelUtils.isComposedSchema(schema) && !supportsAdditionalPropertiesWithComposedSchema) {
return;
}
if (addPropProp != null) {
property.setAdditionalProperties(addPropProp);
}
}
@ -6157,6 +6169,7 @@ public class DefaultCodegen implements CodegenConfig {
}
private void addVarsRequiredVarsAdditionalProps(Schema schema, IJsonSchemaValidationProperties property){
setAddProps(schema, property);
if (!"object".equals(schema.getType())) {
return;
}
@ -6178,7 +6191,6 @@ public class DefaultCodegen implements CodegenConfig {
property.setHasRequired(true);
}
}
setAddProps(schema, property);
}
private void addJsonSchemaForBodyRequestInCaseItsNotPresent(CodegenParameter codegenParameter, RequestBody body) {

View File

@ -240,7 +240,8 @@ public class DefaultCodegenTest {
}
@Test
public void testAdditionalPropertiesV2Spec() {
public void testAdditionalPropertiesV2SpecDisallowAdditionalPropertiesIfNotPresentTrue() {
// this is the legacy config that most of our tooling uses
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/additional-properties-for-testing.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.setOpenAPI(openAPI);
@ -254,111 +255,269 @@ public class DefaultCodegenTest {
// 'additionalProperties' keyword for this model, hence assert the value to be null.
Assert.assertNull(addProps);
CodegenModel cm = codegen.fromModel("AdditionalPropertiesClass", schema);
Assert.assertNull(cm.getAdditionalProperties());
// When the 'additionalProperties' keyword is not present, the model
// should allow undeclared properties. However, due to bug
// https://github.com/swagger-api/swagger-parser/issues/1369, the swagger
// converter does not retain the value of the additionalProperties.
Map<String, Schema> m = schema.getProperties();
Schema child = m.get("map_string");
Map<String, Schema> modelPropSchems = schema.getProperties();
Schema map_string_sc = modelPropSchems.get("map_string");
CodegenProperty map_string_cp = null;
Schema map_with_additional_properties_sc = modelPropSchems.get("map_with_additional_properties");
CodegenProperty map_with_additional_properties_cp = null;
Schema map_without_additional_properties_sc = modelPropSchems.get("map_without_additional_properties");;
CodegenProperty map_without_additional_properties_cp = null;
for(CodegenProperty cp: cm.vars) {
if (cp.baseName.equals("map_string")) {
map_string_cp = cp;
} else if (cp.baseName.equals("map_with_additional_properties")) {
map_with_additional_properties_cp = cp;
} else if (cp.baseName.equals("map_without_additional_properties")) {
map_without_additional_properties_cp = cp;
}
}
// map_string
// This property has the following inline schema.
// additionalProperties:
// type: string
Assert.assertNotNull(child);
Assert.assertNotNull(child.getAdditionalProperties());
Assert.assertNotNull(map_string_sc);
Assert.assertNotNull(map_string_sc.getAdditionalProperties());
Assert.assertNotNull(map_string_cp.getAdditionalProperties());
child = m.get("map_with_additional_properties");
// map_with_additional_properties
// This property has the following inline schema.
// additionalProperties: true
Assert.assertNotNull(child);
Assert.assertNotNull(map_with_additional_properties_sc);
// It is unfortunate that child.getAdditionalProperties() returns null for a V2 schema.
// We cannot differentiate between 'additionalProperties' not present and
// additionalProperties: true.
Assert.assertNull(child.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, child);
Assert.assertNull(map_with_additional_properties_sc.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, map_with_additional_properties_sc);
Assert.assertNull(addProps);
Assert.assertNull(map_with_additional_properties_cp.getAdditionalProperties());
child = m.get("map_without_additional_properties");
// map_without_additional_properties
// This property has the following inline schema.
// additionalProperties: false
Assert.assertNotNull(child);
Assert.assertNotNull(map_without_additional_properties_sc);
// It is unfortunate that child.getAdditionalProperties() returns null for a V2 schema.
// We cannot differentiate between 'additionalProperties' not present and
// additionalProperties: false.
Assert.assertNull(child.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, child);
Assert.assertNull(map_without_additional_properties_sc.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, map_without_additional_properties_sc);
Assert.assertNull(addProps);
Assert.assertNull(map_without_additional_properties_cp.getAdditionalProperties());
// check of composed schema model
String schemaName = "Parent";
schema = openAPI.getComponents().getSchemas().get(schemaName);
cm = codegen.fromModel(schemaName, schema);
Assert.assertNull(cm.getAdditionalProperties());
}
@Test
public void testAdditionalPropertiesV3Spec() {
public void testAdditionalPropertiesV2SpecDisallowAdditionalPropertiesIfNotPresentFalse() {
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/additional-properties-for-testing.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.setOpenAPI(openAPI);
codegen.setDisallowAdditionalPropertiesIfNotPresent(false);
codegen.supportsAdditionalPropertiesWithComposedSchema = true;
/*
When this DisallowAdditionalPropertiesIfNotPresent is false:
for CodegenModel/CodegenParameter/CodegenProperty/CodegenResponse.getAdditionalProperties
if the input additionalProperties is False or unset (null)
.getAdditionalProperties is set to AnyTypeSchema
For the False value this is incorrect, but it is the best that we can do because of this bug:
https://github.com/swagger-api/swagger-parser/issues/1369 where swagger parser
sets both null/False additionalProperties to null
*/
Schema schema = openAPI.getComponents().getSchemas().get("AdditionalPropertiesClass");
Assert.assertEquals(schema.getAdditionalProperties(), null);
Schema addProps = ModelUtils.getAdditionalProperties(openAPI, schema);
// The petstore-with-fake-endpoints-models-for-testing.yaml does not set the
// 'additionalProperties' keyword for this model, hence assert the value to be null.
Assert.assertNull(addProps);
CodegenModel cm = codegen.fromModel("AdditionalPropertiesClass", schema);
Assert.assertNotNull(cm.getAdditionalProperties());
// When the 'additionalProperties' keyword is not present, the model
// should allow undeclared properties. However, due to bug
// https://github.com/swagger-api/swagger-parser/issues/1369, the swagger
// converter does not retain the value of the additionalProperties.
Map<String, Schema> modelPropSchems = schema.getProperties();
Schema map_string_sc = modelPropSchems.get("map_string");
CodegenProperty map_string_cp = null;
Schema map_with_additional_properties_sc = modelPropSchems.get("map_with_additional_properties");
CodegenProperty map_with_additional_properties_cp = null;
Schema map_without_additional_properties_sc = modelPropSchems.get("map_without_additional_properties");;
CodegenProperty map_without_additional_properties_cp = null;
for(CodegenProperty cp: cm.vars) {
if (cp.baseName.equals("map_string")) {
map_string_cp = cp;
} else if (cp.baseName.equals("map_with_additional_properties")) {
map_with_additional_properties_cp = cp;
} else if (cp.baseName.equals("map_without_additional_properties")) {
map_without_additional_properties_cp = cp;
}
}
// map_string
// This property has the following inline schema.
// additionalProperties:
// type: string
Assert.assertNotNull(map_string_sc);
Assert.assertNotNull(map_string_sc.getAdditionalProperties());
Assert.assertNotNull(map_string_cp.getAdditionalProperties());
// map_with_additional_properties
// This property has the following inline schema.
// additionalProperties: true
Assert.assertNotNull(map_with_additional_properties_sc);
// It is unfortunate that child.getAdditionalProperties() returns null for a V2 schema.
// We cannot differentiate between 'additionalProperties' not present and
// additionalProperties: true.
Assert.assertNull(map_with_additional_properties_sc.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, map_with_additional_properties_sc);
Assert.assertNull(addProps);
Assert.assertNotNull(map_with_additional_properties_cp.getAdditionalProperties());
// map_without_additional_properties
// This property has the following inline schema.
// additionalProperties: false
Assert.assertNotNull(map_without_additional_properties_sc);
// It is unfortunate that child.getAdditionalProperties() returns null for a V2 schema.
// We cannot differentiate between 'additionalProperties' not present and
// additionalProperties: false.
Assert.assertNull(map_without_additional_properties_sc.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, map_without_additional_properties_sc);
Assert.assertNull(addProps);
Assert.assertNotNull(map_without_additional_properties_cp.getAdditionalProperties());
// check of composed schema model
String schemaName = "Parent";
schema = openAPI.getComponents().getSchemas().get(schemaName);
cm = codegen.fromModel(schemaName, schema);
Assert.assertNotNull(cm.getAdditionalProperties());
}
@Test
public void testAdditionalPropertiesV3SpecDisallowAdditionalPropertiesIfNotPresentFalse() {
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/python/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.setDisallowAdditionalPropertiesIfNotPresent(false);
codegen.supportsAdditionalPropertiesWithComposedSchema = true;
codegen.setOpenAPI(openAPI);
Schema schema = openAPI.getComponents().getSchemas().get("AdditionalPropertiesClass");
Assert.assertNull(schema.getAdditionalProperties());
Schema componentSchema = openAPI.getComponents().getSchemas().get("AdditionalPropertiesClass");
Assert.assertNull(componentSchema.getAdditionalProperties());
// When the 'additionalProperties' keyword is not present, the schema may be
// extended with any undeclared properties.
Schema addProps = ModelUtils.getAdditionalProperties(openAPI, schema);
Schema addProps = ModelUtils.getAdditionalProperties(openAPI, componentSchema);
Assert.assertNotNull(addProps);
Assert.assertTrue(addProps instanceof ObjectSchema);
CodegenModel cm = codegen.fromModel("AdditionalPropertiesClass", schema);
CodegenModel cm = codegen.fromModel("AdditionalPropertiesClass", componentSchema);
Assert.assertNotNull(cm.getAdditionalProperties());
Map<String, Schema> m = schema.getProperties();
Schema child = m.get("map_with_undeclared_properties_string");
Map<String, Schema> modelPropSchems = componentSchema.getProperties();
Schema map_with_undeclared_properties_string_sc = modelPropSchems.get("map_with_undeclared_properties_string");
CodegenProperty map_with_undeclared_properties_string_cp = null;
Schema map_with_undeclared_properties_anytype_1_sc = modelPropSchems.get("map_with_undeclared_properties_anytype_1");
CodegenProperty map_with_undeclared_properties_anytype_1_cp = null;
Schema map_with_undeclared_properties_anytype_2_sc = modelPropSchems.get("map_with_undeclared_properties_anytype_2");
CodegenProperty map_with_undeclared_properties_anytype_2_cp = null;
Schema map_with_undeclared_properties_anytype_3_sc = modelPropSchems.get("map_with_undeclared_properties_anytype_3");
CodegenProperty map_with_undeclared_properties_anytype_3_cp = null;
Schema empty_map_sc = modelPropSchems.get("empty_map");
CodegenProperty empty_map_cp = null;
for(CodegenProperty cp: cm.vars) {
if (cp.baseName.equals("map_with_undeclared_properties_string")) {
map_with_undeclared_properties_string_cp = cp;
} else if (cp.baseName.equals("map_with_undeclared_properties_anytype_1")) {
map_with_undeclared_properties_anytype_1_cp = cp;
} else if (cp.baseName.equals("map_with_undeclared_properties_anytype_2")) {
map_with_undeclared_properties_anytype_2_cp = cp;
} else if (cp.baseName.equals("map_with_undeclared_properties_anytype_3")) {
map_with_undeclared_properties_anytype_3_cp = cp;
} else if (cp.baseName.equals("empty_map")) {
empty_map_cp = cp;
}
}
// map_with_undeclared_properties_string
// This property has the following inline schema.
// additionalProperties:
// type: string
Assert.assertNotNull(child);
Assert.assertNotNull(child.getAdditionalProperties());
Assert.assertNotNull(map_with_undeclared_properties_string_sc);
Assert.assertNotNull(map_with_undeclared_properties_string_sc.getAdditionalProperties());
Assert.assertNotNull(map_with_undeclared_properties_string_cp.getAdditionalProperties());
child = m.get("map_with_undeclared_properties_anytype_1");
// map_with_undeclared_properties_anytype_1
// This property does not use the additionalProperties keyword,
// which means by default undeclared properties are allowed.
Assert.assertNotNull(child);
Assert.assertNull(child.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, child);
Assert.assertNotNull(map_with_undeclared_properties_anytype_1_sc);
Assert.assertNull(map_with_undeclared_properties_anytype_1_sc.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, map_with_undeclared_properties_anytype_1_sc);
Assert.assertNotNull(addProps);
Assert.assertTrue(addProps instanceof ObjectSchema);
Assert.assertNotNull(map_with_undeclared_properties_anytype_1_cp.getAdditionalProperties());
child = m.get("map_with_undeclared_properties_anytype_2");
// map_with_undeclared_properties_anytype_2
// This property does not use the additionalProperties keyword,
// which means by default undeclared properties are allowed.
Assert.assertNotNull(child);
Assert.assertNull(child.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, child);
Assert.assertNotNull(map_with_undeclared_properties_anytype_2_sc);
Assert.assertNull(map_with_undeclared_properties_anytype_2_sc.getAdditionalProperties());
addProps = ModelUtils.getAdditionalProperties(openAPI, map_with_undeclared_properties_anytype_2_sc);
Assert.assertNotNull(addProps);
Assert.assertTrue(addProps instanceof ObjectSchema);
Assert.assertNotNull(map_with_undeclared_properties_anytype_2_cp.getAdditionalProperties());
child = m.get("map_with_undeclared_properties_anytype_3");
// map_with_undeclared_properties_anytype_3
// This property has the following inline schema.
// additionalProperties: true
Assert.assertNotNull(child);
Assert.assertNotNull(map_with_undeclared_properties_anytype_3_sc);
// Unlike the V2 spec, in V3 we CAN differentiate between 'additionalProperties' not present and
// additionalProperties: true.
Assert.assertNotNull(child.getAdditionalProperties());
Assert.assertEquals(child.getAdditionalProperties(), Boolean.TRUE);
addProps = ModelUtils.getAdditionalProperties(openAPI, child);
Assert.assertNotNull(map_with_undeclared_properties_anytype_3_sc.getAdditionalProperties());
Assert.assertEquals(map_with_undeclared_properties_anytype_3_sc.getAdditionalProperties(), Boolean.TRUE);
addProps = ModelUtils.getAdditionalProperties(openAPI, map_with_undeclared_properties_anytype_3_sc);
Assert.assertNotNull(addProps);
Assert.assertTrue(addProps instanceof ObjectSchema);
Assert.assertNotNull(map_with_undeclared_properties_anytype_3_cp.getAdditionalProperties());
child = m.get("empty_map");
// empty_map
// This property has the following inline schema.
// additionalProperties: false
Assert.assertNotNull(child);
Assert.assertNotNull(empty_map_sc);
// Unlike the V2 spec, in V3 we CAN differentiate between 'additionalProperties' not present and
// additionalProperties: false.
Assert.assertNotNull(child.getAdditionalProperties());
Assert.assertEquals(child.getAdditionalProperties(), Boolean.FALSE);
addProps = ModelUtils.getAdditionalProperties(openAPI, child);
Assert.assertNotNull(empty_map_sc.getAdditionalProperties());
Assert.assertEquals(empty_map_sc.getAdditionalProperties(), Boolean.FALSE);
addProps = ModelUtils.getAdditionalProperties(openAPI, empty_map_sc);
Assert.assertNull(addProps);
Assert.assertNull(empty_map_cp.getAdditionalProperties());
// check of composed schema model
String schemaName = "SomeObject";
Schema schema = openAPI.getComponents().getSchemas().get(schemaName);
cm = codegen.fromModel(schemaName, schema);
Assert.assertNotNull(cm.getAdditionalProperties());
}
@Test
public void testAdditionalPropertiesV3SpecLegacy() {
public void testAdditionalPropertiesV3SpecDisallowAdditionalPropertiesIfNotPresentTrue() {
// As per OAS spec, when the 'additionalProperties' keyword is not present, the schema may be
// extended with any undeclared properties.
// However, in legacy 'additionalProperties' mode, this is interpreted as
// 'no additional properties are allowed'.
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml");
DefaultCodegen codegen = new DefaultCodegen();
codegen.setDisallowAdditionalPropertiesIfNotPresent(true);
@ -367,10 +526,6 @@ public class DefaultCodegenTest {
Schema schema = openAPI.getComponents().getSchemas().get("AdditionalPropertiesClass");
Assert.assertNull(schema.getAdditionalProperties());
// As per OAS spec, when the 'additionalProperties' keyword is not present, the schema may be
// extended with any undeclared properties.
// However, in legacy 'additionalProperties' mode, this is interpreted as
// 'no additional properties are allowed'.
Schema addProps = ModelUtils.getAdditionalProperties(openAPI, schema);
Assert.assertNull(addProps);
}

View File

@ -9,6 +9,18 @@ info:
host: petstore.swagger.io:80
basePath: /v2
definitions:
Grandparent:
type: object
properties:
radioWaves:
type: boolean
Parent:
allOf:
- $ref: '#/definitions/Grandparent'
- type: object
properties:
teleVision:
type: boolean
AdditionalPropertiesClass:
type: object
properties: