diff --git a/src/main/kotlin/io/openapiprocessor/core/converter/SchemaInfo.kt b/src/main/kotlin/io/openapiprocessor/core/converter/SchemaInfo.kt index f23c845b..a6dcc699 100644 --- a/src/main/kotlin/io/openapiprocessor/core/converter/SchemaInfo.kt +++ b/src/main/kotlin/io/openapiprocessor/core/converter/SchemaInfo.kt @@ -203,13 +203,13 @@ class SchemaInfo( } /** - * iterate over items + * iterate over composed items */ fun eachItemOf(action: (info: SchemaInfo) -> Unit) { schema?.getItems()?.forEachIndexed { index, schema -> action(SchemaInfo( path = path, - name = "${name}-of-${index}", + name = "${name}_${itemOf()!!.capitalize()}_${index}", schema = schema, resolver = resolver )) diff --git a/src/main/kotlin/io/openapiprocessor/core/model/Api.kt b/src/main/kotlin/io/openapiprocessor/core/model/Api.kt index d2b87472..f1bad813 100644 --- a/src/main/kotlin/io/openapiprocessor/core/model/Api.kt +++ b/src/main/kotlin/io/openapiprocessor/core/model/Api.kt @@ -17,6 +17,7 @@ package io.openapiprocessor.core.model import io.openapiprocessor.core.model.datatypes.DataType +import io.openapiprocessor.core.model.datatypes.ModelDataType import io.openapiprocessor.core.model.datatypes.ObjectDataType import io.openapiprocessor.core.model.datatypes.StringEnumDataType import java.util.function.Consumer @@ -60,6 +61,10 @@ class Api( models.getObjectDataTypes().forEach(action) } + fun forEachModelDataType(action: Consumer) { + models.getModelDataTypes().forEach(action) + } + fun forEachEnumDataType(action: Consumer) { models.getEnumDataTypes().forEach(action) } diff --git a/src/main/kotlin/io/openapiprocessor/core/model/DataTypes.kt b/src/main/kotlin/io/openapiprocessor/core/model/DataTypes.kt index e6a8812c..55296a52 100644 --- a/src/main/kotlin/io/openapiprocessor/core/model/DataTypes.kt +++ b/src/main/kotlin/io/openapiprocessor/core/model/DataTypes.kt @@ -16,10 +16,7 @@ package io.openapiprocessor.core.model -import io.openapiprocessor.core.model.datatypes.DataType -import io.openapiprocessor.core.model.datatypes.MappedDataType -import io.openapiprocessor.core.model.datatypes.ObjectDataType -import io.openapiprocessor.core.model.datatypes.StringEnumDataType +import io.openapiprocessor.core.model.datatypes.* /** * Container of data types from OpenAPI '#/component/schemas'. @@ -52,6 +49,21 @@ class DataTypes { .toList() } + /** + * provides the *object* data types (model classes) used by the api endpoints. + * For this objects the processor will create POJOs classes. + * + * experimental: will probably replace getObjectDataTypes(). + * + * @return list of object data types + */ + fun getModelDataTypes(): Collection { + return types.values + .filterIsInstance() + .filter { it.isModel() } + .toList() + } + /** * provides the enum data types (model classes) used by the api endpoints. * For this objects the processor will create enum classes. diff --git a/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ComposedObjectDataType.kt b/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ComposedObjectDataType.kt index a92b0b44..fd023183 100644 --- a/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ComposedObjectDataType.kt +++ b/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ComposedObjectDataType.kt @@ -30,7 +30,7 @@ class ComposedObjectDataType( constraints: DataTypeConstraints? = null, deprecated: Boolean = false -): DataTypeBase(constraints, deprecated) { +): DataTypeBase(constraints, deprecated), ModelDataType { override fun getName(): String { return type @@ -51,8 +51,27 @@ class ComposedObjectDataType( .toSet() } + // todo find better name override fun isComposed(): Boolean { return of != "allOf" } + override fun isModel(): Boolean { + return of == "allOf" + } + + override fun getProperties(): Map { + val properties = linkedMapOf() + + if (of == "allOf") { + items.forEach { + if (it is ObjectDataType) { + properties.putAll(it.getProperties()) + } + } + } + + return properties + } + } diff --git a/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ModelDataType.kt b/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ModelDataType.kt new file mode 100644 index 00000000..9dd4d5ab --- /dev/null +++ b/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ModelDataType.kt @@ -0,0 +1,14 @@ +/* + * Copyright © 2020 https://github.com/openapi-processor/openapi-processor-core + * PDX-License-Identifier: Apache-2.0 + */ + +package io.openapiprocessor.core.model.datatypes + +interface ModelDataType: DataType { + + fun isModel(): Boolean + + fun getProperties(): Map + +} diff --git a/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ObjectDataType.kt b/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ObjectDataType.kt index 4bf0a6b0..15dc615e 100644 --- a/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ObjectDataType.kt +++ b/src/main/kotlin/io/openapiprocessor/core/model/datatypes/ObjectDataType.kt @@ -30,7 +30,7 @@ class ObjectDataType( constraints: DataTypeConstraints? = null, deprecated: Boolean = false -): DataTypeBase(constraints, deprecated) { +): DataTypeBase(constraints, deprecated), ModelDataType { override fun getName(): String { return type @@ -55,7 +55,16 @@ class ObjectDataType( return properties[name]!! } + @Deprecated("do not override groovys getProperties()", ReplaceWith("getProperties()")) fun getObjectProperties(): Map { + return getProperties() + } + + override fun isModel(): Boolean { + return true + } + + override fun getProperties(): Map { return properties } diff --git a/src/main/kotlin/io/openapiprocessor/core/writer/java/ApiWriter.kt b/src/main/kotlin/io/openapiprocessor/core/writer/java/ApiWriter.kt index 1a4a775a..51ae82fa 100644 --- a/src/main/kotlin/io/openapiprocessor/core/writer/java/ApiWriter.kt +++ b/src/main/kotlin/io/openapiprocessor/core/writer/java/ApiWriter.kt @@ -21,9 +21,9 @@ import com.google.googlejavaformat.java.JavaFormatterOptions import io.openapiprocessor.core.converter.ApiOptions import io.openapiprocessor.core.model.Api import io.openapiprocessor.core.model.Interface -import io.openapiprocessor.core.model.datatypes.ObjectDataType import io.openapiprocessor.core.model.datatypes.StringEnumDataType import io.openapiprocessor.core.misc.toURL +import io.openapiprocessor.core.model.datatypes.ModelDataType import java.io.BufferedWriter import java.io.StringWriter import java.io.Writer @@ -73,7 +73,7 @@ class ApiWriter( } private fun writeObjectDataTypes(api: Api) { - api.forEachObjectDataType { + api.forEachModelDataType { val target = modelFolder.resolve ("${it.getName()}.java") val writer = BufferedWriter(PathWriter(target)) writeDataType(writer, it) @@ -96,7 +96,7 @@ class ApiWriter( writer.write(format(raw.toString())) } - private fun writeDataType(writer: Writer, dataType: ObjectDataType) { + private fun writeDataType(writer: Writer, dataType: ModelDataType) { val raw = StringWriter() dataTypeWriter.write(raw, dataType) writer.write(format(raw.toString ())) diff --git a/src/main/kotlin/io/openapiprocessor/core/writer/java/DataTypeWriter.kt b/src/main/kotlin/io/openapiprocessor/core/writer/java/DataTypeWriter.kt index 72f36d45..eae6e872 100644 --- a/src/main/kotlin/io/openapiprocessor/core/writer/java/DataTypeWriter.kt +++ b/src/main/kotlin/io/openapiprocessor/core/writer/java/DataTypeWriter.kt @@ -18,7 +18,7 @@ package io.openapiprocessor.core.writer.java import io.openapiprocessor.core.converter.ApiOptions import io.openapiprocessor.core.model.datatypes.DataType -import io.openapiprocessor.core.model.datatypes.ObjectDataType +import io.openapiprocessor.core.model.datatypes.ModelDataType import java.io.Writer /** @@ -33,7 +33,7 @@ class DataTypeWriter( private val validationAnnotations: BeanValidationFactory = BeanValidationFactory() ) { - fun write(target: Writer, dataType: ObjectDataType) { + fun write(target: Writer, dataType: ModelDataType) { headerWriter.write(target) target.write("package ${dataType.getPackageName()};\n\n") @@ -52,16 +52,14 @@ class DataTypeWriter( target.write("public class ${dataType.getName()} {\n\n") - val propertyNames = dataType.getObjectProperties().keys - propertyNames.forEach { - val javaPropertyName = toCamelCase(it) - val propDataType = dataType.getObjectProperty(it) - target.write(getProp(it, javaPropertyName, propDataType)) + val properties = dataType.getProperties() + properties.forEach { (propName, propDataType) -> + val javaPropertyName = toCamelCase(propName) + target.write(getProp(propName, javaPropertyName, propDataType)) } - propertyNames.forEach { - val javaPropertyName = toCamelCase(it) - val propDataType = dataType.getObjectProperty(it) + properties.forEach { (propName, propDataType) -> + val javaPropertyName = toCamelCase(propName) target.write(getGetter(javaPropertyName, propDataType)) target.write(getSetter(javaPropertyName, propDataType)) } @@ -122,14 +120,14 @@ class DataTypeWriter( return result } - private fun collectImports(packageName: String, dataType: ObjectDataType): List { + private fun collectImports(packageName: String, dataType: ModelDataType): List { val imports = mutableSetOf() imports.add("com.fasterxml.jackson.annotation.JsonProperty") imports.addAll(dataType.getReferencedImports()) if (apiOptions.beanValidation) { - dataType.getObjectProperties().values.forEach { + dataType.getProperties().values.forEach { imports.addAll(validationAnnotations.collectImports(it)) } } diff --git a/src/test/kotlin/io/openapiprocessor/core/model/datatypes/ComposedObjectDataTypeSpec.kt b/src/test/kotlin/io/openapiprocessor/core/model/datatypes/ComposedObjectDataTypeSpec.kt new file mode 100644 index 00000000..b5448e72 --- /dev/null +++ b/src/test/kotlin/io/openapiprocessor/core/model/datatypes/ComposedObjectDataTypeSpec.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 https://github.com/openapi-processor/openapi-processor-core + * PDX-License-Identifier: Apache-2.0 + */ + +package io.openapiprocessor.core.model.datatypes + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class ComposedObjectDataTypeSpec : StringSpec({ + + "loop properties of allOf objects as if it was a single object" { + val composed = ComposedObjectDataType("Foo", "pkg", "allOf", listOf( + ObjectDataType("Foo", "pkg", linkedMapOf( + Pair("foo", StringDataType()), + Pair("foobar", StringDataType()) + )), + ObjectDataType("Bar", "pkg", linkedMapOf( + Pair("bar", StringDataType()), + Pair("barfoo", StringDataType()) + )) + )) + + val properties = composed.getProperties() + + properties.keys shouldBe linkedSetOf("foo", "foobar", "bar", "barfoo") + } + +}) diff --git a/src/test/kotlin/io/openapiprocessor/core/writer/java/ApiWriterSpec.kt b/src/test/kotlin/io/openapiprocessor/core/writer/java/ApiWriterSpec.kt index 5cf9a10e..accbd4b8 100644 --- a/src/test/kotlin/io/openapiprocessor/core/writer/java/ApiWriterSpec.kt +++ b/src/test/kotlin/io/openapiprocessor/core/writer/java/ApiWriterSpec.kt @@ -218,7 +218,7 @@ class ApiWriterSpec: StringSpec({ "re-formats model sources" { val dtWriter = io.mockk.mockk() every { dtWriter.write(any(), any()) } answers { - arg(0).write(" class \n ${arg(1).getName()} { }\n") + arg(0).write(" class \n ${arg(1).getName()} { }\n") } val dt = DataTypes() diff --git a/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorEndToEndTest.groovy b/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorEndToEndTest.groovy index eac15690..89233b69 100644 --- a/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorEndToEndTest.groovy +++ b/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorEndToEndTest.groovy @@ -37,7 +37,8 @@ class ProcessorEndToEndTest extends ProcessorTestBase { 'ref-loop-array', 'ref-parameter', 'ref-parameter-with-primitive-mapping', - 'response-content-multiple-no-content' + 'response-content-multiple-no-content', + 'schema-composed-allof' ] @Parameterized.Parameters(name = "{0}") diff --git a/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorPendingTest.groovy b/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorPendingTest.groovy index 8da54ce2..1bf09804 100644 --- a/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorPendingTest.groovy +++ b/src/testInt/groovy/com/github/hauner/openapi/processor/core/ProcessorPendingTest.groovy @@ -32,8 +32,8 @@ class ProcessorPendingTest extends ProcessorTestBase { @Parameterized.Parameters(name = "{0}") static Collection sources () { return [ - new TestSet(name: 'ref-loop-array', processor: new TestProcessor(), parser: ParserType.SWAGGER), - new TestSet(name: 'ref-loop-array', processor: new TestProcessor(), parser: ParserType.OPENAPI4J) + new TestSet(name: 'schema-composed-allof', processor: new TestProcessor(), parser: ParserType.SWAGGER), + new TestSet(name: 'schema-composed-allof', processor: new TestProcessor(), parser: ParserType.OPENAPI4J) ] } diff --git a/src/testInt/resources/tests/schema-composed-allof/generated.yaml b/src/testInt/resources/tests/schema-composed-allof/generated.yaml new file mode 100644 index 00000000..496f414f --- /dev/null +++ b/src/testInt/resources/tests/schema-composed-allof/generated.yaml @@ -0,0 +1,5 @@ +items: + - generated/api/Api.java + - generated/model/QueryResponse200.java + - generated/model/QueryResponse200_AllOf_0.java + - generated/model/QueryResponse200_AllOf_1.java diff --git a/src/testInt/resources/tests/schema-composed-allof/generated/api/Api.java b/src/testInt/resources/tests/schema-composed-allof/generated/api/Api.java new file mode 100644 index 00000000..a794c316 --- /dev/null +++ b/src/testInt/resources/tests/schema-composed-allof/generated/api/Api.java @@ -0,0 +1,16 @@ +/* + * This class is auto generated by https://github.com/hauner/openapi-processor-core. + * TEST ONLY. + */ + +package generated.api; + +import annotation.Mapping; +import generated.model.QueryResponse200; + +public interface Api { + + @Mapping("/query") + QueryResponse200 getQuery(); + +} diff --git a/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200.java b/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200.java new file mode 100644 index 00000000..8d953f20 --- /dev/null +++ b/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200.java @@ -0,0 +1,34 @@ +/* + * This class is auto generated by https://github.com/hauner/openapi-processor-core. + * TEST ONLY. + */ + +package generated.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class QueryResponse200 { + + @JsonProperty("prop1") + private String prop1; + + @JsonProperty("prop2") + private String prop2; + + public String getProp1() { + return prop1; + } + + public void setProp1(String prop1) { + this.prop1 = prop1; + } + + public String getProp2() { + return prop2; + } + + public void setProp2(String prop2) { + this.prop2 = prop2; + } + +} diff --git a/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200_AllOf_0.java b/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200_AllOf_0.java new file mode 100644 index 00000000..cc12225f --- /dev/null +++ b/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200_AllOf_0.java @@ -0,0 +1,23 @@ +/* + * This class is auto generated by https://github.com/hauner/openapi-processor-core. + * TEST ONLY. + */ + +package generated.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class QueryResponse200_AllOf_0 { + + @JsonProperty("prop1") + private String prop1; + + public String getProp1() { + return prop1; + } + + public void setProp1(String prop1) { + this.prop1 = prop1; + } + +} diff --git a/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200_AllOf_1.java b/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200_AllOf_1.java new file mode 100644 index 00000000..7096aa76 --- /dev/null +++ b/src/testInt/resources/tests/schema-composed-allof/generated/model/QueryResponse200_AllOf_1.java @@ -0,0 +1,23 @@ +/* + * This class is auto generated by https://github.com/hauner/openapi-processor-core. + * TEST ONLY. + */ + +package generated.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class QueryResponse200_AllOf_1 { + + @JsonProperty("prop2") + private String prop2; + + public String getProp2() { + return prop2; + } + + public void setProp2(String prop2) { + this.prop2 = prop2; + } + +} diff --git a/src/testInt/resources/tests/schema-composed-allof/inputs.yaml b/src/testInt/resources/tests/schema-composed-allof/inputs.yaml new file mode 100644 index 00000000..a7b02dbc --- /dev/null +++ b/src/testInt/resources/tests/schema-composed-allof/inputs.yaml @@ -0,0 +1,2 @@ +items: + - inputs/openapi.yaml diff --git a/src/testInt/resources/tests/schema-composed-allof/inputs/openapi.yaml b/src/testInt/resources/tests/schema-composed-allof/inputs/openapi.yaml new file mode 100644 index 00000000..2b53c2a4 --- /dev/null +++ b/src/testInt/resources/tests/schema-composed-allof/inputs/openapi.yaml @@ -0,0 +1,23 @@ +openapi: 3.0.2 +info: + title: merge allOf into same object + version: 1.0.0 + +paths: + /query: + get: + responses: + '200': + description: create result from allOff object + content: + application/json: + schema: + allOf: + - type: object + properties: + prop1: + type: string + - type: object + properties: + prop2: + type: string diff --git a/src/testInt/resources/tests/schema-composed/generated.yaml b/src/testInt/resources/tests/schema-composed/generated.yaml index 2a29e5e0..a76defcb 100644 --- a/src/testInt/resources/tests/schema-composed/generated.yaml +++ b/src/testInt/resources/tests/schema-composed/generated.yaml @@ -3,3 +3,4 @@ items: - generated/model/One.java - generated/model/Two.java - generated/model/Three.java + - generated/model/FooAllOf.java diff --git a/src/testInt/resources/tests/schema-composed/generated/model/FooAllOf.java b/src/testInt/resources/tests/schema-composed/generated/model/FooAllOf.java new file mode 100644 index 00000000..2ab83784 --- /dev/null +++ b/src/testInt/resources/tests/schema-composed/generated/model/FooAllOf.java @@ -0,0 +1,45 @@ +/* + * This class is auto generated by https://github.com/hauner/openapi-processor-core. + * TEST ONLY. + */ + +package generated.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class FooAllOf { + + @JsonProperty("one") + private String one; + + @JsonProperty("two") + private String two; + + @JsonProperty("three") + private String three; + + public String getOne() { + return one; + } + + public void setOne(String one) { + this.one = one; + } + + public String getTwo() { + return two; + } + + public void setTwo(String two) { + this.two = two; + } + + public String getThree() { + return three; + } + + public void setThree(String three) { + this.three = three; + } + +}