Skip to content

Commit

Permalink
Add support for additional properties with simple types. (#350)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjbooms authored Jan 27, 2025
1 parent 37932d5 commit 3611773
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 11 deletions.
17 changes: 12 additions & 5 deletions src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,18 @@ object PropertyUtils {
constructorBuilder.addParameter(constructorParameter.build())

val value =
if (typeInfo is KotlinTypeInfo.MapTypeAdditionalProperties) {
Map::class.asTypeName()
.parameterizedBy(String::class.asTypeName(), parameterizedType.maybeMakeMapValueNullable())
} else {
parameterizedType
when (typeInfo) {
is KotlinTypeInfo.MapTypeAdditionalProperties -> {
Map::class.asTypeName()
.parameterizedBy(String::class.asTypeName(), parameterizedType.maybeMakeMapValueNullable())
}
is KotlinTypeInfo.SimpleTypedAdditionalProperties -> {
(typeInfo as KotlinTypeInfo.SimpleTypedAdditionalProperties).parameterizedType.modelKClass.asTypeName()
.maybeMakeMapValueNullable()
}
else -> {
parameterizedType
}
}.maybeMakeMapValueNullable()

val getterSpecBuilder = FunSpec.builder("get")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ class ModelGenerator(
typeInfo.parameterizedType,
),
)
is KotlinTypeInfo.SimpleTypedAdditionalProperties -> createMutableMapOfStringToType(
toModelType(
basePackage,
typeInfo.parameterizedType,
),
)

else -> className
}
Expand Down
7 changes: 6 additions & 1 deletion src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN
data class GeneratedTypedAdditionalProperties(val simpleClassName: String) :
KotlinTypeInfo(GeneratedType::class, simpleClassName)

data class SimpleTypedAdditionalProperties(val parameterizedType: KotlinTypeInfo) : KotlinTypeInfo(Map::class)

data class MapTypeAdditionalProperties(val parameterizedType: KotlinTypeInfo) :
KotlinTypeInfo(Map::class)

Expand Down Expand Up @@ -106,12 +108,15 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN

OasType.Object -> Object(ModelNameRegistry.getOrRegister(schema, enclosingSchema))
OasType.Map ->
Map(from(schema.additionalPropertiesSchema, "", enclosingSchema))
Map(from(schema.additionalPropertiesSchema, OasType.ADDITIONAL_PROPERTIES_VALUE, enclosingSchema))

OasType.TypedObjectAdditionalProperties -> GeneratedTypedAdditionalProperties(
ModelNameRegistry.getOrRegister(schema, valueSuffix = schema.isInlinedTypedAdditionalProperties())
)

OasType.SimpleTypedAdditionalProperties ->
SimpleTypedAdditionalProperties(from(schema, OasType.ADDITIONAL_PROPERTIES_VALUE))

OasType.UntypedObjectAdditionalProperties -> UntypedObjectAdditionalProperties
OasType.UntypedObject -> UntypedObject
OasType.UnknownAdditionalProperties -> UnknownAdditionalProperties
Expand Down
13 changes: 12 additions & 1 deletion src/main/kotlin/com/cjbooms/fabrikt/model/OasType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.isMapTypeAdditionalProper
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSchemaLess
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleMapDefinition
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleOneOfAnyDefinition
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleTypedAdditionalProperties
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isStringDefinitionWithFormat
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isTypedAdditionalProperties
import com.cjbooms.fabrikt.util.KaizenParserExtensions.isUnknownAdditionalProperties
Expand Down Expand Up @@ -48,17 +49,24 @@ sealed class OasType(

object TypedObjectAdditionalProperties :
OasType("object", specialization = Specialization.TYPED_OBJECT_ADDITIONAL_PROPERTIES)
object SimpleTypedAdditionalProperties :
OasType(WILD_CARD_TYPE, specialization = Specialization.SIMPLE_TYPED_ADDITIONAL_PROPERTIES)

object TypedMapAdditionalProperties :
OasType("object", specialization = Specialization.TYPED_MAP_ADDITIONAL_PROPERTIES)

companion object {
private const val WILD_CARD_TYPE: String = "wildcard"
const val ADDITIONAL_PROPERTIES_VALUE: String = "additionalPropertiesValue"
fun Schema.toOasType(oasKey: String): OasType =
values(OasType::class)
.filter {
it.type == safeType() ||
it.type == WILD_CARD_TYPE && getSpecialization(oasKey) == Specialization.ONE_OF_ANY
it.type == WILD_CARD_TYPE &&
listOf(
Specialization.ONE_OF_ANY,
Specialization.SIMPLE_TYPED_ADDITIONAL_PROPERTIES
).contains(getSpecialization(oasKey))
}
.filter { it.specialization == getSpecialization(oasKey) }
.filter { it.format == format || it.format == null }
Expand Down Expand Up @@ -87,6 +95,8 @@ sealed class OasType(
isUnknownAdditionalProperties(oasKey) -> Specialization.UNKNOWN_ADDITIONAL_PROPERTIES
isSimpleOneOfAnyDefinition() -> Specialization.ONE_OF_ANY
isSchemaLess() -> Specialization.UNTYPED_OBJECT
oasKey != ADDITIONAL_PROPERTIES_VALUE && isSimpleTypedAdditionalProperties(oasKey) ->
Specialization.SIMPLE_TYPED_ADDITIONAL_PROPERTIES
else -> Specialization.NONE
}
}
Expand All @@ -96,6 +106,7 @@ sealed class OasType(
MAP,
UNKNOWN_ADDITIONAL_PROPERTIES,
TYPED_OBJECT_ADDITIONAL_PROPERTIES,
SIMPLE_TYPED_ADDITIONAL_PROPERTIES,
TYPED_MAP_ADDITIONAL_PROPERTIES,
UNTYPED_OBJECT_ADDITIONAL_PROPERTIES,
UNTYPED_OBJECT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ object KaizenParserExtensions {
fun Schema.isTypedAdditionalProperties(oasKey: String) = type == OasType.Object.type &&
(getSchemaNameInParent() == "additionalProperties" || oasKey == "additionalProperties") && properties?.isEmpty() != true

fun Schema.isSimpleTypedAdditionalProperties(oasKey: String) = isSimpleType() &&
(getSchemaNameInParent() == "additionalProperties" || oasKey == "additionalProperties") && properties?.isEmpty() == true

fun Schema.isMapTypeAdditionalProperties(oasKey: String) = type == OasType.Object.type &&
(oasKey == "additionalProperties") && properties?.isEmpty() == true &&
hasAdditionalProperties()
Expand Down
34 changes: 30 additions & 4 deletions src/test/resources/examples/mapExamples/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ components:
type: integer
additionalProperties:
$ref: '#/components/schemas/SomeRef'

SomeRef:
type: object
properties:
other_text:
type: string
other_number:
type: integer

ComplexObjectWithMapsOfMaps:
type: object
required:
Expand All @@ -133,10 +133,10 @@ components:
minProperties: 1
additionalProperties:
$ref: '#/components/schemas/BasicObject'

BasicObject:
type: object
properties:
properties:
one:
type: string
StringMap:
Expand All @@ -159,3 +159,29 @@ components:
additionalProperties:
type: object


BasicObjectWithStringMap:
type: object
properties:
one:
type: string
additionalProperties:
type: string


BasicObjectWithNumberMap:
type: object
properties:
one:
type: string
additionalProperties:
type: integer


BasicObjectWithBooleanMap:
type: object
properties:
one:
type: string
additionalProperties:
type: boolean
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package examples.mapExamples.models

import com.fasterxml.jackson.`annotation`.JsonAnyGetter
import com.fasterxml.jackson.`annotation`.JsonAnySetter
import com.fasterxml.jackson.`annotation`.JsonIgnore
import com.fasterxml.jackson.`annotation`.JsonProperty
import kotlin.Boolean
import kotlin.String
import kotlin.collections.Map
import kotlin.collections.MutableMap

public data class BasicObjectWithBooleanMap(
@param:JsonProperty("one")
@get:JsonProperty("one")
public val one: String? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Boolean?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Boolean?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Boolean?) {
properties[name] = value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package examples.mapExamples.models

import com.fasterxml.jackson.`annotation`.JsonAnyGetter
import com.fasterxml.jackson.`annotation`.JsonAnySetter
import com.fasterxml.jackson.`annotation`.JsonIgnore
import com.fasterxml.jackson.`annotation`.JsonProperty
import kotlin.Int
import kotlin.String
import kotlin.collections.Map
import kotlin.collections.MutableMap

public data class BasicObjectWithNumberMap(
@param:JsonProperty("one")
@get:JsonProperty("one")
public val one: String? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Int?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Int?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Int?) {
properties[name] = value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package examples.mapExamples.models

import com.fasterxml.jackson.`annotation`.JsonAnyGetter
import com.fasterxml.jackson.`annotation`.JsonAnySetter
import com.fasterxml.jackson.`annotation`.JsonIgnore
import com.fasterxml.jackson.`annotation`.JsonProperty
import kotlin.String
import kotlin.collections.Map
import kotlin.collections.MutableMap

public data class BasicObjectWithStringMap(
@param:JsonProperty("one")
@get:JsonProperty("one")
public val one: String? = null,
@get:JsonIgnore
public val properties: MutableMap<String, String?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, String?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: String?) {
properties[name] = value
}
}

0 comments on commit 3611773

Please sign in to comment.