diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java index e9d25fd5ca74dd93b03daa344a9277e821bfd342..f2f0ee97ca39dabcc0a3dbe609d44bf30381fe11 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java @@ -74,7 +74,26 @@ public class CodegenModel implements IJsonSchemaValidationProperties { public Set<String> allMandatory = new TreeSet<String>(); // with parent's required properties public Set<String> imports = new TreeSet<String>(); - public boolean hasVars, emptyVars, hasMoreModels, hasEnums, isEnum, isNullable, hasRequired, hasOptional, isArrayModel, hasChildren, isMapModel, isDeprecated; + public boolean hasVars, emptyVars, hasMoreModels, hasEnums, isEnum; + /** + * Indicates the OAS schema specifies "nullable: true". + */ + public boolean isNullable; + /** + * Indicates the type has at least one required property. + */ + public boolean hasRequired; + /** + * Indicates the type has at least one optional property. + */ + public boolean hasOptional; + public boolean isArrayModel; + public boolean hasChildren; + public boolean isMapModel; + /** + * Indicates the OAS schema specifies "deprecated: true". + */ + public boolean isDeprecated; public boolean hasOnlyReadOnly = true; // true if all properties are read-only public ExternalDocumentation externalDocumentation; 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 82cebbf022456acf051620a5c8c70073d9a6f843..6032edf19ddf1cafc2b0d9284506dee0d1f1d2b1 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 @@ -1774,6 +1774,10 @@ public class DefaultCodegen implements CodegenConfig { **/ @SuppressWarnings("static-method") public String getSchemaType(Schema schema) { + if (schema == null) { + // This is indicative of a bug in codegen. + throw new RuntimeException("getSchemaType schema argument is null"); + } if (schema instanceof ComposedSchema) { // composed schema ComposedSchema cs = (ComposedSchema) schema; // Get the interfaces, i.e. the set of elements under 'allOf', 'anyOf' or 'oneOf'. @@ -1886,7 +1890,9 @@ public class DefaultCodegen implements CodegenConfig { */ private String getSingleSchemaType(Schema schema) { Schema unaliasSchema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping); - + if (unaliasSchema == null) { + throw new RuntimeException("Schema '" + schema.getName() + "' is invalid"); + } if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema // get the schema/model name from $ref String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref()); @@ -2010,15 +2016,15 @@ public class DefaultCodegen implements CodegenConfig { } /** - * Output the type declaration of the property + * Output the language-specific type declaration of the property. * * @param schema property schema * @return a string presentation of the property type */ public String getTypeDeclaration(Schema schema) { if (schema == null) { - LOGGER.warn("Null schema found. Default type to `NULL_SCHMEA_ERR`"); - return "NULL_SCHMEA_ERR"; + LOGGER.warn("Null schema found. Default type to `NULL_SCHEMA_ERR`"); + return "NULL_SCHEMA_ERR"; } String oasType = getSchemaType(schema); @@ -2100,6 +2106,70 @@ public class DefaultCodegen implements CodegenConfig { return camelize(modelNamePrefix + "_" + name + "_" + modelNameSuffix); } + /** + * Returns a composed model that encapsulates the JSON schema "any type". + * Its value can be any of null, integer, boolean, number, string, array or map. + */ + protected Schema getAnyTypeSchema(String name, Schema schema) { + if (!ModelUtils.isAnyTypeSchema(schema)) { + throw new RuntimeException("Schema '" + name + "' is not 'any type'"); + } + ComposedSchema cs = (ComposedSchema) new ComposedSchema() + .addAnyOfItem(new ObjectSchema().type("null")) + .addAnyOfItem(new BooleanSchema()) + .addAnyOfItem(new StringSchema() + .minLength(schema.getMinLength()) + .maxLength(schema.getMaxLength()) + .pattern(schema.getPattern()) + ) + .addAnyOfItem(new IntegerSchema() + .minimum(schema.getMinimum()) + .maximum(schema.getMaximum()) + .exclusiveMinimum(schema.getExclusiveMinimum()) + .exclusiveMaximum(schema.getExclusiveMaximum()) + .multipleOf(schema.getMultipleOf()) + ) + .addAnyOfItem(new NumberSchema() + .minimum(schema.getMinimum()) + .maximum(schema.getMaximum()) + .exclusiveMinimum(schema.getExclusiveMinimum()) + .exclusiveMaximum(schema.getExclusiveMaximum()) + .multipleOf(schema.getMultipleOf()) + ) + .name(name); + + // The map keys must be strings and the values can be anything. + cs.addAnyOfItem(new MapSchema() + .additionalProperties(true) + .minProperties(schema.getMinProperties()) + .maxProperties(schema.getMaxProperties()) + .required(schema.getRequired()) + ); + // The array items can be anything. + cs.addAnyOfItem(new ArraySchema() + .minItems(schema.getMinItems()) + .maxItems(schema.getMaxItems()) + .uniqueItems(schema.getUniqueItems()) + ); + if (schema != null) { + cs.setTitle(schema.getTitle()); + cs.setDescription(schema.getDescription()); + } + return cs; + } + + // Returns a model that encapsulates the JSON schema "any type". Its value + // can be any of null, integer, boolean, number, string, array or map. + // "Any type" is a schema that does not have the "type" attribute + // specified in the OpenAPI schema. That means the value can be any valid + // payload, i.e. the null value, boolean, string, integer, number, + // array or map. + // Numerical payloads may match more than one type, for example "2" may + // match integer and number. Hence the use of 'anyOf'. + public CodegenModel getAnyTypeModel(String name, Schema schema) { + return fromModel(name, getAnyTypeSchema(name, schema)); + } + /** * Convert OAS Model object to Codegen Model object * @@ -2121,6 +2191,11 @@ public class DefaultCodegen implements CodegenConfig { return null; } + if (ModelUtils.isAnyTypeSchema(schema)) { + // "Any type" means the payload can be any type, e.g. integer, number, object, array... + return getAnyTypeModel(name, schema); + } + CodegenModel m = CodegenModelFactory.newInstance(CodegenModelType.MODEL); if (reservedWords.contains(name)) { @@ -2155,7 +2230,6 @@ public class DefaultCodegen implements CodegenConfig { m.xmlNamespace = schema.getXml().getNamespace(); m.xmlName = schema.getXml().getName(); } - if (ModelUtils.isArraySchema(schema)) { m.isArrayModel = true; m.arrayModelType = fromProperty(name, schema).complexType; @@ -2801,7 +2875,7 @@ public class DefaultCodegen implements CodegenConfig { * Convert OAS Property object to Codegen Property object * * @param name name of the property - * @param p OAS property object + * @param p OAS property schema * @return Codegen Property object */ public CodegenProperty fromProperty(String name, Schema p) { @@ -2814,6 +2888,9 @@ public class DefaultCodegen implements CodegenConfig { // unalias schema p = ModelUtils.unaliasSchema(this.openAPI, p, importMapping); + if (ModelUtils.isAnyTypeSchema(p)) { + p = getAnyTypeSchema(name, p); + } CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY); ModelUtils.syncValidationProperties(p, property); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java index d4c0a8514e6ece3ffd89bdacf605222f8f35f050..465313c3ed498f58f033ff8341c8a78710005ebd 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java @@ -327,10 +327,23 @@ public abstract class AbstractGoCodegen extends DefaultCodegen implements Codege if (ModelUtils.isArraySchema(p)) { ArraySchema ap = (ArraySchema) p; Schema inner = ap.getItems(); - return "[]" + getTypeDeclaration(ModelUtils.unaliasSchema(this.openAPI, inner)); + // Per JSON schema specification, the "items" attribute in an Array schema + // is optional. When "items" is not specified, the elements of the array + // may be anything at all. + if (inner != null) { + inner = ModelUtils.unaliasSchema(this.openAPI, inner); + } + String typDecl; + if (inner != null) { + typDecl = getTypeDeclaration(inner); + } else { + typDecl = "interface{}"; + } + return "[]" + typDecl; } else if (ModelUtils.isMapSchema(p)) { Schema inner = ModelUtils.getAdditionalProperties(p); - return getSchemaType(p) + "[string]" + getTypeDeclaration(ModelUtils.unaliasSchema(this.openAPI, inner)); + inner = ModelUtils.unaliasSchema(this.openAPI, inner); + return getSchemaType(p) + "[string]" + getTypeDeclaration(inner); } //return super.getTypeDeclaration(p); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java index 60a004b6bea86b27bbb9b99946c9f6df30040f7d..2ecbea9db8550f0dd0d39b66e96bbe534b2f8c18 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java @@ -910,7 +910,14 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { } else if (ModelUtils.isArraySchema(p)) { ArraySchema ap = (ArraySchema) p; Schema inner = ap.getItems(); - return prefix + "[" + getTypeString(inner, "", "") + "]" + fullSuffix; + if (inner == null) { + // Per JSON schema specification, the array "items" attribute is optional. + // When "items" is not specified, the elements of the array + // may be anything at all. + return prefix + "[bool, date, datetime, dict, float, int, list, str, none_type]" + fullSuffix; + } else { + return prefix + "[" + getTypeString(inner, "", "") + "]" + fullSuffix; + } } if (ModelUtils.isFileSchema(p)) { return prefix + "file_type" + fullSuffix; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java index e9133bc605d59192b368bcfd195c1a56243df6eb..6f2e8c2a2ee33fbbe93bd3a3cf191a4cfe5c9c48 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java @@ -287,7 +287,7 @@ public class DartModelTest { @Test(dataProvider = "modelNames", description = "avoid inner class") public void modelNameTest(String name, String expectedName) { OpenAPI openAPI = TestUtils.createOpenAPI(); - final Schema model = new Schema(); + final Schema model = new ObjectSchema(); final DefaultCodegen codegen = new DartClientCodegen(); codegen.setOpenAPI(openAPI); final CodegenModel cm = codegen.fromModel(name, model); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java index aea9437cbfc1d98b8bde1df52925f569e9fe2080..44392622fd436523b9ea28c3964ba8a5e9de6273 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java @@ -25,6 +25,7 @@ import io.swagger.v3.oas.models.media.DateTimeSchema; import io.swagger.v3.oas.models.media.IntegerSchema; import io.swagger.v3.oas.models.media.MapSchema; import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.StringSchema; import org.openapitools.codegen.ClientOptInput; import org.openapitools.codegen.CodegenConstants; @@ -375,7 +376,7 @@ public class DartDioModelTest { @Test(dataProvider = "modelNames", description = "avoid inner class") public void modelNameTest(String name, String expectedName) { OpenAPI openAPI = TestUtils.createOpenAPI(); - final Schema model = new Schema(); + final Schema model = new ObjectSchema(); final DefaultCodegen codegen = new DartDioClientCodegen(); codegen.setOpenAPI(openAPI); final CodegenModel cm = codegen.fromModel(name, model); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java index 28276ab4daa052a7a530943a5ac6d1128135fb31..871f9c4db88f346aa52e02fd9cf5f399b18f291d 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/go/GoModelTest.java @@ -294,7 +294,7 @@ public class GoModelTest { @Test(dataProvider = "modelNames", description = "avoid inner class") public void modelNameTest(String name, String expectedName) { - final Schema model = new Schema(); + final Schema model = new ObjectSchema(); final DefaultCodegen codegen = new GoClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema(name, model); codegen.setOpenAPI(openAPI); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java index 1d01d7d2d6e98ca7af1c432c7d6a2b7fbb5d65b7..c5e25b5503b60ff4e306b91a6ba5375164e5f4b3 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaModelTest.java @@ -279,6 +279,21 @@ public class JavaModelTest { Assert.assertFalse(property.isContainer); } + @Test(description = "test 'any type' schema") + public void anyTypeSchemaTest() { + // Create a schema without any constraint. + final Schema schema = new Schema() + .description("a sample model"); + final DefaultCodegen codegen = new JavaClientCodegen(); + OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema); + codegen.setOpenAPI(openAPI); + final CodegenModel cm = codegen.fromModel("sample", schema); + // + Assert.assertEquals(cm.name, "sample"); + Assert.assertEquals(cm.classname, "Sample"); + Assert.assertEquals(cm.description, "a sample model"); + } + @Test(description = "convert a model with complex list property") public void complexListPropertyTest() { final Schema schema = new Schema() @@ -713,7 +728,7 @@ public class JavaModelTest { @Test(dataProvider = "modelNames", description = "avoid inner class") public void modelNameTest(String name, String expectedName) { - final Schema schema = new Schema(); + final Schema schema = new ObjectSchema(); final DefaultCodegen codegen = new JavaClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema(name, schema); codegen.setOpenAPI(openAPI); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinReservedWordsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinReservedWordsTest.java index 58727aa38032b21b666bf12a0607bfba23ee4e1f..0354ab756e77a6b2ab66355a5eb41e158dc6a091 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinReservedWordsTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinReservedWordsTest.java @@ -2,6 +2,7 @@ package org.openapitools.codegen.kotlin; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; import org.openapitools.codegen.*; @@ -55,7 +56,7 @@ public class KotlinReservedWordsTest { @Test(dataProvider = "reservedWords") public void testReservedWordsAsModels(String reservedWord) { final DefaultCodegen codegen = new KotlinClientCodegen(); - final Schema schema = new Schema(); + final Schema schema = new ObjectSchema(); final String escaped = "`" + reservedWord + "`"; final String titleCased = StringUtils.camelize(reservedWord, false); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java index 989e0ca84b0f398b2cb2d324224e060f91808497..850d4d3fdaa8dfb30ab5d21d3e4317b24b19ffd1 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/php/PhpModelTest.java @@ -290,7 +290,7 @@ public class PhpModelTest { @Test(dataProvider = "modelNames", description = "avoid inner class") public void modelNameTest(String name, String expectedName) { - final Schema model = new Schema(); + final Schema model = new ObjectSchema(); final DefaultCodegen codegen = new PhpClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema(name, model); codegen.setOpenAPI(openAPI); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scalaakka/ScalaAkkaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scalaakka/ScalaAkkaClientCodegenTest.java index 34e536bdf906d589198b6d4cd57b1a4aadd548f3..1c73534bebbc8e6277925109d0db03da2e3ceb03 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scalaakka/ScalaAkkaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scalaakka/ScalaAkkaClientCodegenTest.java @@ -398,7 +398,7 @@ public class ScalaAkkaClientCodegenTest { @Test(description = "strip model name") public void stripModelNameTest() throws Exception { - final Schema model = new Schema() + final Schema model = new ObjectSchema() .description("a map model"); final DefaultCodegen codegen = new ScalaAkkaClientCodegen(); OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);