Skip to content
This repository was archived by the owner on Mar 16, 2025. It is now read-only.

Commit 633f8b7

Browse files
committed
#78, detect oneOf to interface condition
1 parent 0c43608 commit 633f8b7

File tree

9 files changed

+190
-7
lines changed

9 files changed

+190
-7
lines changed

src/main/kotlin/io/openapiprocessor/core/converter/ApiOptions.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ class ApiOptions {
4747
*/
4848
var modelNameSuffix = String.Empty
4949

50+
/**
51+
* enable/disable generation of a common interface for an `oneOf` list of objects. All objects
52+
* implement that interface.
53+
*/
54+
var oneOfInterface = false
55+
5056
/**
5157
* enable/disable the code formatter (optional).
5258
*/

src/main/kotlin/io/openapiprocessor/core/converter/DataTypeConverter.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,28 @@ class DataTypeConverter(
105105

106106
objectType = AllOfObjectDataType(
107107
DataTypeName(schemaInfo.getName(), getTypeNameWithSuffix(schemaInfo.getName())),
108-
listOf(options.packageName, "model").joinToString ("."),
108+
listOf(options.packageName, "model").joinToString("."),
109109
items,
110110
schemaInfo.getDeprecated()
111111
)
112+
} else if (shouldGenerateOneOfInterface(items) && schemaInfo.isComposedOneOf()) {
113+
val constraints = DataTypeConstraints(
114+
nullable = schemaInfo.getNullable(),
115+
required = schemaInfo.getRequired()
116+
)
117+
118+
objectType = InterfaceDataType (
119+
DataTypeName(schemaInfo.getName(), getTypeNameWithSuffix(schemaInfo.getName())),
120+
listOf(options.packageName, "model").joinToString("."),
121+
items,
122+
constraints,
123+
schemaInfo.getDeprecated(),
124+
Documentation(description = schemaInfo.description)
125+
)
126+
127+
items.forEach {
128+
(it as ModelDataType).implementsDataType = objectType
129+
}
112130
} else {
113131
objectType = AnyOneOfObjectDataType(
114132
schemaInfo.getName(),
@@ -124,6 +142,11 @@ class DataTypeConverter(
124142
return objectType
125143
}
126144

145+
private fun shouldGenerateOneOfInterface(items: List<DataType>): Boolean {
146+
return options.oneOfInterface
147+
&& items.size == items.filterIsInstance<ModelDataType>().count()
148+
}
149+
127150
private fun createArrayDataType(schemaInfo: SchemaInfo, dataTypes: DataTypes): DataType {
128151
val itemSchemaInfo = schemaInfo.buildForItem()
129152
val item = convert(itemSchemaInfo, dataTypes)

src/main/kotlin/io/openapiprocessor/core/converter/SchemaInfo.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ open class SchemaInfo(
340340
return itemOf().equals("allOf")
341341
}
342342

343+
fun isComposedOneOf(): Boolean {
344+
return itemOf().equals("oneOf")
345+
}
346+
343347
fun isTypeLess(): Boolean {
344348
return schema?.getType() == null
345349
}

src/main/kotlin/io/openapiprocessor/core/model/DataTypeCollector.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ class DataTypeCollector(
5656
is PropertyDataType -> {
5757
collect(dataType.dataType)
5858
}
59+
is InterfaceDataType -> {
60+
dataTypes.addRef(dataType.getName())
61+
dataType.items.forEach {
62+
collect(it)
63+
}
64+
}
5965
}
6066
}
6167

src/main/kotlin/io/openapiprocessor/core/model/datatypes/AllOfObjectDataType.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class AllOfObjectDataType(
1313
private val pkg: String,
1414
private val items: List<DataType> = emptyList(),
1515
override val deprecated: Boolean = false
16-
): DataType, ModelDataType {
16+
): ModelDataType {
17+
override var implementsDataType: InterfaceDataType? = null
1718

1819
override fun getName(): String {
1920
return name.id
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2022 https://github.com/openapi-processor/openapi-processor-core
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.model.datatypes
7+
8+
import io.openapiprocessor.core.model.Documentation
9+
10+
/**
11+
* represents an OpenAPI object that is generated as interface.
12+
*
13+
* only used with onOf elements are all objects. It will represent the common interface that all
14+
* items implement.
15+
*/
16+
class InterfaceDataType(
17+
private val name: DataTypeName,
18+
private val pkg: String,
19+
val items: List<DataType> = emptyList(),
20+
override val constraints: DataTypeConstraints? = null,
21+
override val deprecated: Boolean = false,
22+
override val documentation: Documentation? = null
23+
): DataType {
24+
25+
override fun getName(): String {
26+
return name.id
27+
}
28+
29+
override fun getTypeName(): String {
30+
return name.type
31+
}
32+
33+
override fun getPackageName(): String {
34+
return pkg
35+
}
36+
37+
override fun getImports(): Set<String> {
38+
return setOf("${getPackageName()}.${getTypeName()}")
39+
}
40+
}

src/main/kotlin/io/openapiprocessor/core/model/datatypes/ModelDataType.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
package io.openapiprocessor.core.model.datatypes
77

88
interface ModelDataType: DataType {
9+
/**
10+
* implements this interface.
11+
*/
12+
var implementsDataType: InterfaceDataType?
913

1014
/**
1115
* loop object properties.

src/main/kotlin/io/openapiprocessor/core/model/datatypes/ObjectDataType.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ open class ObjectDataType(
1818
override val constraints: DataTypeConstraints? = null,
1919
override val deprecated: Boolean = false,
2020
override val documentation: Documentation? = null
21-
): DataType, ModelDataType {
21+
): ModelDataType {
22+
override var implementsDataType: InterfaceDataType? = null
2223

2324
override fun getName(): String {
2425
return name.id
@@ -38,10 +39,7 @@ open class ObjectDataType(
3839

3940
override val referencedImports: Set<String>
4041
get() {
41-
return properties.values
42-
.map { it.getImports() }
43-
.flatten()
44-
.toSet()
42+
return propertiesImports + implementsImports
4543
}
4644

4745
fun addObjectProperty(name: String, type: PropertyDataType) {
@@ -64,4 +62,16 @@ open class ObjectDataType(
6462
for (p in properties) action(p.key, p.value)
6563
}
6664

65+
private val propertiesImports: Set<String>
66+
get() {
67+
return properties.values
68+
.map { it.getImports() }
69+
.flatten()
70+
.toSet()
71+
}
72+
73+
private val implementsImports: Set<String>
74+
get() {
75+
return implementsDataType?.referencedImports ?: emptySet()
76+
}
6777
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2022 https://github.com/openapi-processor/openapi-processor-core
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.converter
7+
8+
import io.kotest.core.spec.style.StringSpec
9+
import io.kotest.matchers.shouldBe
10+
import io.kotest.matchers.types.shouldBeInstanceOf
11+
import io.openapiprocessor.core.model.DataTypes
12+
import io.openapiprocessor.core.model.HttpMethod
13+
import io.openapiprocessor.core.model.datatypes.InterfaceDataType
14+
import io.openapiprocessor.core.model.datatypes.ModelDataType
15+
import io.openapiprocessor.core.support.getBodySchemaInfo
16+
import io.openapiprocessor.core.support.parse
17+
18+
class DataTypeConverterOneOfSpec: StringSpec({
19+
20+
"creates interface and implementing model classes for oneOf with objects" {
21+
val dataTypes = DataTypes()
22+
val options = ApiOptions()
23+
options.oneOfInterface = true
24+
25+
val openApi = parse("""
26+
openapi: 3.0.2
27+
info:
28+
title: API
29+
version: 1.0.0
30+
31+
paths:
32+
/foo:
33+
post:
34+
requestBody:
35+
content:
36+
application/json:
37+
schema:
38+
${'$'}ref: '#/components/schemas/Foo'
39+
responses:
40+
'204':
41+
description: empty
42+
43+
components:
44+
schemas:
45+
46+
Foo:
47+
type: object
48+
properties:
49+
myProperties:
50+
${'$'}ref: '#/components/schemas/GenericProperties'
51+
52+
GenericProperties:
53+
oneOf:
54+
- ${'$'}ref: '#/components/schemas/SpecificPropertiesOne'
55+
- ${'$'}ref: '#/components/schemas/SpecificPropertiesTwo'
56+
57+
SpecificPropertiesOne:
58+
type: object
59+
properties:
60+
foo:
61+
type: string
62+
maxLength: 200
63+
64+
SpecificPropertiesTwo:
65+
type: object
66+
properties:
67+
bar:
68+
type: string
69+
maxLength: 100
70+
71+
""".trimIndent())
72+
73+
val schemaInfo = openApi.getBodySchemaInfo("Foo",
74+
"/foo", HttpMethod.POST, "application/json")
75+
76+
// when:
77+
val converter = DataTypeConverter(options)
78+
val datatype = converter.convert(schemaInfo, dataTypes)
79+
80+
// then:
81+
val ifDataType = dataTypes.find("GenericProperties")
82+
ifDataType.shouldBeInstanceOf<InterfaceDataType>()
83+
ifDataType.items.size shouldBe 2
84+
ifDataType.items.forEach {
85+
(it as ModelDataType).implementsDataType shouldBe ifDataType
86+
}
87+
}
88+
89+
})

0 commit comments

Comments
 (0)