Skip to content

Commit

Permalink
ISSUE#25 When generating inlined object definitions prefix the derive…
Browse files Browse the repository at this point in the history
…d model name with the name of the enclosing top-level model. (#31)
  • Loading branch information
cjbooms authored Dec 15, 2020
1 parent 33a6aec commit 31e5153
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.isReferenceObjectDefiniti
import com.cjbooms.fabrikt.util.KaizenParserExtensions.mappingKey
import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeName
import com.cjbooms.fabrikt.util.KaizenParserExtensions.toMapValueClassName
import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName
import com.cjbooms.fabrikt.util.NormalisedString.toModelClassName
import com.reprezen.kaizen.oasparser.model3.Discriminator
import com.reprezen.kaizen.oasparser.model3.Schema
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
Expand Down Expand Up @@ -111,7 +113,9 @@ class JacksonModelGenerator(
.filterNot { it.isSimpleType || it.isInlineableMapDefinition }
.flatMap {
if (it.properties.isNotEmpty() || it.typeInfo is KotlinTypeInfo.Enum) {
listOf(buildPrimaryModel(it, it.properties)) + buildInLinedModels(it.properties)
val primaryModel = buildPrimaryModel(it, it.properties)
val inlinedModels = buildInLinedModels(it.properties, it.schema)
listOf(primaryModel) + inlinedModels
} else emptyList()
}.toMutableSet()

Expand All @@ -136,27 +140,32 @@ class JacksonModelGenerator(
}
}

private fun buildInLinedModels(topLevelProperties: Collection<PropertyInfo>): List<TypeSpec> =
private fun buildInLinedModels(
topLevelProperties: Collection<PropertyInfo>,
enclosingSchema: Schema
): List<TypeSpec> =
topLevelProperties.flatMap {
val enclosingModelName = enclosingSchema.toModelClassName()
when (it) {
is PropertyInfo.SingleRef ->
is PropertyInfo.ObjectInlinedField -> {
val props = it.schema.topLevelProperties(HTTP_SETTINGS, enclosingSchema)
val currentModel = standardDataClass(it.name.toModelClassName(enclosingModelName), props)
val inlinedModels = buildInLinedModels(props, enclosingSchema)
inlinedModels + currentModel
}
is PropertyInfo.ObjectRefField ->
when {
it.schema.isInlinedObjectDefinition() -> it.schema.topLevelProperties(HTTP_SETTINGS)
.let { props ->
buildInLinedModels(props) +
standardDataClass(it.name.toModelClassName(), props)
}
it.schema.isReferenceObjectDefinition() -> it.schema.topLevelProperties(HTTP_SETTINGS)
it.schema.isReferenceObjectDefinition() -> it.schema.topLevelProperties(HTTP_SETTINGS, enclosingSchema)
.let { props ->
buildInLinedModels(props) +
buildInLinedModels(props, enclosingSchema) +
standardDataClass(it.schema.safeName().toModelClassName(), props)
}
else -> emptySet()
}
is PropertyInfo.MapField -> buildMapModel(it)?.let { mapModel -> setOf(mapModel) } ?: emptySet()
is PropertyInfo.AdditionalProperties ->
if (it.schema.isComplexTypedAdditionalProperties("additionalProperties")) setOf(
standardDataClass(it.schema.toMapValueClassName(), it.schema.topLevelProperties(HTTP_SETTINGS))
standardDataClass(it.schema.toMapValueClassName(), it.schema.topLevelProperties(HTTP_SETTINGS, enclosingSchema))
)
else emptySet()
is PropertyInfo.Field ->
Expand All @@ -165,11 +174,13 @@ class JacksonModelGenerator(
is PropertyInfo.ListField ->
it.schema.itemsSchema.let { items ->
when {
items.isInlinedObjectDefinition() -> items.topLevelProperties(HTTP_SETTINGS).let { props ->
buildInLinedModels(props) + standardDataClass(it.name.toModelClassName(), props)
items.isInlinedObjectDefinition() -> items.topLevelProperties(HTTP_SETTINGS, enclosingSchema).let { props ->
buildInLinedModels(props, enclosingSchema) + standardDataClass(
it.name.toModelClassName(enclosingModelName), props
)
}
items.isEnumDefinition() ->
setOf(buildEnumClass(KotlinTypeInfo.from(items, "items") as KotlinTypeInfo.Enum))
setOf(buildEnumClass(KotlinTypeInfo.from(items, "items", enclosingModelName) as KotlinTypeInfo.Enum))
else -> emptySet()
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.getEnumValues
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isNotDefined
import com.cjbooms.fabrikt.util.KaizenParserExtensions.toMapValueClassName
import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName
import com.cjbooms.fabrikt.util.NormalisedString.toModelClassName
import com.reprezen.kaizen.oasparser.model3.Schema
import java.math.BigDecimal
import java.time.LocalDate
Expand Down Expand Up @@ -39,7 +40,7 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
}

companion object {
fun from(schema: Schema, oasKey: String = ""): KotlinTypeInfo =
fun from(schema: Schema, oasKey: String = "", enclosingName: String = ""): KotlinTypeInfo =
when (schema.toOasType(oasKey)) {
OasType.Date -> Date
OasType.DateTime -> DateTime
Expand All @@ -55,9 +56,9 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
OasType.Array ->
if (schema.itemsSchema.isNotDefined())
throw IllegalArgumentException("Property ${schema.name} cannot be parsed to a Schema. Check your input")
else Array(from(schema.itemsSchema, oasKey))
OasType.Object -> Object(schema.toModelClassName())
OasType.Map -> Map(from(schema.additionalPropertiesSchema, "additionalProperties"))
else Array(from(schema.itemsSchema, oasKey, enclosingName))
OasType.Object -> Object(schema.toModelClassName(enclosingName.toModelClassName()))
OasType.Map -> Map(from(schema.additionalPropertiesSchema, "additionalProperties", enclosingName))
OasType.TypedProperties -> TypedProperties(schema.toMapValueClassName())
OasType.UntypedProperties -> UntypedProperties
OasType.UntypedObject -> UntypedObject
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/cjbooms/fabrikt/model/ModelInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ data class ModelInfo(
val maybeSuperType: ModelInfo? = schema.getSuperType(api)?.let { ModelInfo(it.name, it, api) }
val childSchemas: Collection<String> = parentToChildren[key].orEmpty()
val typeInfo: KotlinTypeInfo = KotlinTypeInfo.from(schema, name)
val properties: Collection<PropertyInfo> = schema.topLevelProperties(PropertyInfo.HTTP_SETTINGS)
val properties: Collection<PropertyInfo> = schema.topLevelProperties(PropertyInfo.HTTP_SETTINGS, schema)

@Suppress("UNCHECKED_CAST")
companion object {
Expand Down
26 changes: 24 additions & 2 deletions src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.hasAdditionalProperties
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isDiscriminatorProperty
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInLinedObjectUnderAllOf
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlineableMapDefinition
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedObjectDefinition
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isPolymorphicSuperType
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isRequired
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSchemaLess
import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeType
import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName
import com.cjbooms.fabrikt.util.NormalisedString.camelCase
import com.reprezen.kaizen.oasparser.model3.OpenApi3
import com.reprezen.kaizen.oasparser.model3.Schema
Expand Down Expand Up @@ -81,8 +83,17 @@ sealed class PropertyInfo {
isInherited = settings.markAsInherited,
parentSchema = this
)
else if (property.value.isInlinedObjectDefinition())
ObjectInlinedField(
isRequired = isRequired(property, settings.markReadWriteOnlyOptional, settings.markAllOptional),
oasKey = property.key,
schema = property.value,
isInherited = settings.markAsInherited,
parentSchema = this,
enclosingSchema = enclosingSchema
)
else
SingleRef(
ObjectRefField(
isRequired(property, settings.markReadWriteOnlyOptional, settings.markAllOptional),
property.key,
property.value,
Expand Down Expand Up @@ -198,7 +209,7 @@ sealed class PropertyInfo {
override val typeInfo: KotlinTypeInfo = KotlinTypeInfo.from(schema, oasKey)
}

data class SingleRef(
data class ObjectRefField(
override val isRequired: Boolean,
override val oasKey: String,
val schema: Schema,
Expand All @@ -208,6 +219,17 @@ sealed class PropertyInfo {
override val typeInfo: KotlinTypeInfo = KotlinTypeInfo.from(schema, oasKey)
}

data class ObjectInlinedField(
override val isRequired: Boolean,
override val oasKey: String,
val schema: Schema,
override val isInherited: Boolean,
val parentSchema: Schema,
val enclosingSchema: Schema?
) : PropertyInfo() {
override val typeInfo: KotlinTypeInfo = KotlinTypeInfo.from(schema, oasKey, enclosingSchema?.toModelClassName() ?: "")
}

data class AdditionalProperties(
val schema: Schema,
override val isInherited: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ object KaizenParserExtensions {
fun Schema.isReferenceObjectDefinition() =
isObjectType() && !isSchemaLess() && !Overlay.of(this).pathFromRoot.contains("properties")

fun Schema.toModelClassName() = safeName().toModelClassName()
fun Schema.toModelClassName(enclosingClassName: String = "") = enclosingClassName + safeName().toModelClassName()

fun Schema.toMapValueClassName() = safeName().toMapValueClassName()

Expand Down
4 changes: 1 addition & 3 deletions src/main/kotlin/com/cjbooms/fabrikt/util/NormalisedString.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ object NormalisedString {

fun String.camelCase(): String = this.pascalCase().decapitalize()

fun String.toEntityName(): String = "${this.toModelClassName()}Entity"

fun String.toModelClassName(): String = this.pascalCase()
fun String.toModelClassName(parentModelName: String = ""): String = parentModelName + this.pascalCase()

fun String.toMapValueClassName(): String = "${this.pascalCase()}Value"

Expand Down
25 changes: 24 additions & 1 deletion src/test/resources/examples/inLinedObject/api.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
openapi: 3.0.0
components:
schemas:
ContainsInLinedObject:
FirstInlineObject:
type: object
properties:
generation:
type: object
properties:
call_home:
type: object
required:
- url
properties:
url:
type: string
database_view:
type: object
required:
- view_name
properties:
view_name:
type: string
direct:
type: string

SecondInlineObject:
type: object
properties:
generation:
Expand Down
49 changes: 42 additions & 7 deletions src/test/resources/examples/inLinedObject/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,71 @@ import javax.validation.Valid
import javax.validation.constraints.NotNull
import kotlin.String

data class ContainsInLinedObject(
data class FirstInlineObject(
@param:JsonProperty("generation")
@get:JsonProperty("generation")
@get:Valid
val generation: Generation? = null
val generation: FirstInlineObjectGeneration? = null
)

data class CallHome(
data class FirstInlineObjectCallHome(
@param:JsonProperty("url")
@get:JsonProperty("url")
@get:NotNull
val url: String
)

data class DatabaseView(
data class FirstInlineObjectDatabaseView(
@param:JsonProperty("view_name")
@get:JsonProperty("view_name")
@get:NotNull
val viewName: String
)

data class Generation(
data class FirstInlineObjectGeneration(
@param:JsonProperty("call_home")
@get:JsonProperty("call_home")
@get:Valid
val callHome: CallHome? = null,
val callHome: FirstInlineObjectCallHome? = null,
@param:JsonProperty("database_view")
@get:JsonProperty("database_view")
@get:Valid
val databaseView: DatabaseView? = null,
val databaseView: FirstInlineObjectDatabaseView? = null,
@param:JsonProperty("direct")
@get:JsonProperty("direct")
val direct: String? = null
)

data class SecondInlineObject(
@param:JsonProperty("generation")
@get:JsonProperty("generation")
@get:Valid
val generation: SecondInlineObjectGeneration? = null
)

data class SecondInlineObjectCallHome(
@param:JsonProperty("url")
@get:JsonProperty("url")
@get:NotNull
val url: String
)

data class SecondInlineObjectDatabaseView(
@param:JsonProperty("view_name")
@get:JsonProperty("view_name")
@get:NotNull
val viewName: String
)

data class SecondInlineObjectGeneration(
@param:JsonProperty("call_home")
@get:JsonProperty("call_home")
@get:Valid
val callHome: SecondInlineObjectCallHome? = null,
@param:JsonProperty("database_view")
@get:JsonProperty("database_view")
@get:Valid
val databaseView: SecondInlineObjectDatabaseView? = null,
@param:JsonProperty("direct")
@get:JsonProperty("direct")
val direct: String? = null
Expand Down
8 changes: 4 additions & 4 deletions src/test/resources/examples/mapExamples/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ data class MapHolder(
@param:JsonProperty("inlined_complex_object_with_untyped_map")
@get:JsonProperty("inlined_complex_object_with_untyped_map")
@get:Valid
val inlinedComplexObjectWithUntypedMap: InlinedComplexObjectWithUntypedMap? = null,
val inlinedComplexObjectWithUntypedMap: MapHolderInlinedComplexObjectWithUntypedMap? = null,
@param:JsonProperty("inlined_complex_object_with_typed_map")
@get:JsonProperty("inlined_complex_object_with_typed_map")
@get:Valid
val inlinedComplexObjectWithTypedMap: InlinedComplexObjectWithTypedMap? = null
val inlinedComplexObjectWithTypedMap: MapHolderInlinedComplexObjectWithTypedMap? = null
)

data class TypedObjectMapValue(
Expand Down Expand Up @@ -113,7 +113,7 @@ data class ComplexObjectWithTypedMap(
}
}

data class InlinedComplexObjectWithUntypedMap(
data class MapHolderInlinedComplexObjectWithUntypedMap(
@param:JsonProperty("text")
@get:JsonProperty("text")
val text: String? = null,
Expand All @@ -138,7 +138,7 @@ data class InlinedComplexObjectWithTypedMapValue(
val otherNumber: Int? = null
)

data class InlinedComplexObjectWithTypedMap(
data class MapHolderInlinedComplexObjectWithTypedMap(
@param:JsonProperty("text")
@get:JsonProperty("text")
val text: String? = null,
Expand Down

0 comments on commit 31e5153

Please sign in to comment.