diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 5155526a4d362c62747518cadf794dba7fe8b9b9..492539bbf89d264dd2f9a36a747eea08dddecc6e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -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) { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java index 17c5864fe008bc58a2f437be4d7254fb76b2fac5..24eb9b6db898e532da2e562091f6576dca925521 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java @@ -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> 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<String, Schema> m = schema.getProperties(); - Schema child = m.get("map_with_undeclared_properties_string"); + // 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); } diff --git a/modules/openapi-generator/src/test/resources/2_0/additional-properties-for-testing.yaml b/modules/openapi-generator/src/test/resources/2_0/additional-properties-for-testing.yaml index 5cfa74c27c8ff7155a93e7df274f85133b124a37..2864ab034c22c60db9ff103fe8680ff1c4241f10 100644 --- a/modules/openapi-generator/src/test/resources/2_0/additional-properties-for-testing.yaml +++ b/modules/openapi-generator/src/test/resources/2_0/additional-properties-for-testing.yaml @@ -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: