Skip to content

Commit

Permalink
Map values should be nullable. (#276)
Browse files Browse the repository at this point in the history
From what I can see, there does not appear to be a way to specify that Map values should be null or non-null in an OpenApi3 specification. We therefore need to make all map values nullable.
I have a case where I need to preserve nulls in an untyped object, and cannot achieve that without this change
  • Loading branch information
cjbooms authored Apr 12, 2024
1 parent 6645d86 commit 05e94d8
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ object PropertyUtils {

val value =
if (typeInfo is KotlinTypeInfo.MapTypeAdditionalProperties) {
Map::class.asTypeName().parameterizedBy(String::class.asTypeName(), parameterizedType)
Map::class.asTypeName()
.parameterizedBy(String::class.asTypeName(), parameterizedType.copy(nullable = true))
} else {
parameterizedType
}
}.copy(nullable = true)
classBuilder.addFunction(
FunSpec.builder("get")
.returns(Map::class.asTypeName().parameterizedBy(String::class.asTypeName(), value))
Expand Down
15 changes: 10 additions & 5 deletions src/main/kotlin/com/cjbooms/fabrikt/generators/TypeFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,31 @@ object TypeFactory {
String::class.asTypeName(),
Map::class.asTypeName().parameterizedBy(
String::class.asTypeName(),
Any::class.asTypeName()
)
Any::class.asTypeName().copy(nullable = true)
).copy(nullable = true)
)

fun createMutableMapOfMapsStringToStringType(type: TypeName) =
ClassName("kotlin.collections", "MutableMap").parameterizedBy(
String::class.asTypeName(),
Map::class.asTypeName().parameterizedBy(
String::class.asTypeName(),
type
)
type.copy(nullable = true)
).copy(nullable = true)
)

fun createMutableMapOfStringToType(type: TypeName) =
ClassName("kotlin.collections", "MutableMap").parameterizedBy(
String::class.asTypeName(),
type
type.copy(nullable = true)
)

fun createMapOfStringToType(type: TypeName) =
Map::class.asClassName().parameterizedBy(
String::class.asTypeName(),
type.copy(nullable = true)
)
fun createMapOfStringToNonNullType(type: TypeName) =
Map::class.asClassName().parameterizedBy(
String::class.asTypeName(),
type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class OkHttpEnhancedClientGenerator(
.addParameter(
ParameterSpec.builder(
ADDITIONAL_HEADERS_PARAMETER_NAME,
TypeFactory.createMapOfStringToType(String::class.asTypeName())
TypeFactory.createMapOfStringToNonNullType(String::class.asTypeName())
)
.defaultValue("emptyMap()")
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class OkHttpSimpleClientGenerator(
.addParameter(
ParameterSpec.builder(
ADDITIONAL_HEADERS_PARAMETER_NAME,
TypeFactory.createMapOfStringToType(String::class.asTypeName())
TypeFactory.createMapOfStringToNonNullType(String::class.asTypeName())
)
.defaultValue("emptyMap()")
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class OpenFeignInterfaceGenerator(
.addParameter(
ParameterSpec.builder(
ADDITIONAL_HEADERS_PARAMETER_NAME,
TypeFactory.createMapOfStringToType(String::class.asTypeName()),
TypeFactory.createMapOfStringToNonNullType(String::class.asTypeName()),
)
.addAnnotation(OpenFeignAnnotations.HEADER_MAP)
.defaultValue("emptyMap()")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.cjbooms.fabrikt.generators.PropertyUtils.addToClass
import com.cjbooms.fabrikt.generators.PropertyUtils.isNullable
import com.cjbooms.fabrikt.generators.TypeFactory.createList
import com.cjbooms.fabrikt.generators.TypeFactory.createMapOfMapsStringToStringAny
import com.cjbooms.fabrikt.generators.TypeFactory.createMapOfStringToNonNullType
import com.cjbooms.fabrikt.generators.TypeFactory.createMapOfStringToType
import com.cjbooms.fabrikt.generators.TypeFactory.createMutableMapOfMapsStringToStringType
import com.cjbooms.fabrikt.generators.TypeFactory.createMutableMapOfStringToType
Expand Down Expand Up @@ -408,7 +409,7 @@ class JacksonModelGenerator(
)
val companion = TypeSpec.companionObjectBuilder()
.addProperty(
PropertySpec.builder("mapping", createMapOfStringToType(enumType))
PropertySpec.builder("mapping", createMapOfStringToNonNullType(enumType))
.initializer("values().associateBy(%T::value)", enumType)
.addModifiers(KModifier.PRIVATE)
.build(),
Expand Down
6 changes: 3 additions & 3 deletions src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ public data class OneOfAdditionalProps(
@get:JsonProperty("second_nested_any_of_prop")
public val secondNestedAnyOfProp: String? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Any> = mutableMapOf(),
public val properties: MutableMap<String, Any?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Any> = properties
public fun `get`(): Map<String, Any?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Any) {
public fun `set`(name: String, `value`: Any?) {
properties[name] = value
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ public data class ExternalObjectTwo(
@get:Valid
public val listOthers: List<ExternalObjectThree>? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Map<String, ExternalObjectFour>> = mutableMapOf(),
public val properties: MutableMap<String, Map<String, ExternalObjectFour?>?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Map<String, ExternalObjectFour>> = properties
public fun `get`(): Map<String, Map<String, ExternalObjectFour?>?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Map<String, ExternalObjectFour>) {
public fun `set`(name: String, `value`: Map<String, ExternalObjectFour?>?) {
properties[name] = value
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ public data class ExternalObjectTwo(
@get:Valid
public val listOthers: List<ExternalObjectThree>? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Map<String, ExternalObjectFour>> = mutableMapOf(),
public val properties: MutableMap<String, Map<String, ExternalObjectFour?>?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Map<String, ExternalObjectFour>> = properties
public fun `get`(): Map<String, Map<String, ExternalObjectFour?>?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Map<String, ExternalObjectFour>) {
public fun `set`(name: String, `value`: Map<String, ExternalObjectFour?>?) {
properties[name] = value
}
}
Expand Down
26 changes: 13 additions & 13 deletions src/test/resources/examples/githubApi/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ public data class BulkEntityDetails(
@get:Valid
public val entities: List<EntityDetails>,
@get:JsonIgnore
public val properties: MutableMap<String, Any> = mutableMapOf(),
public val properties: MutableMap<String, Any?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Any> = properties
public fun `get`(): Map<String, Any?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Any) {
public fun `set`(name: String, `value`: Any?) {
properties[name] = value
}
}
Expand Down Expand Up @@ -121,13 +121,13 @@ public data class EntityDetails(
@get:NotNull
public val id: String,
@get:JsonIgnore
public val properties: MutableMap<String, Any> = mutableMapOf(),
public val properties: MutableMap<String, Any?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Any> = properties
public fun `get`(): Map<String, Any?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Any) {
public fun `set`(name: String, `value`: Any?) {
properties[name] = value
}
}
Expand All @@ -140,15 +140,15 @@ public data class Event(
@param:JsonProperty("data")
@get:JsonProperty("data")
@get:NotNull
public val `data`: Map<String, Any>,
public val `data`: Map<String, Any?>,
@get:JsonIgnore
public val properties: MutableMap<String, Any> = mutableMapOf(),
public val properties: MutableMap<String, Any?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Any> = properties
public fun `get`(): Map<String, Any?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Any) {
public fun `set`(name: String, `value`: Any?) {
properties[name] = value
}
}
Expand All @@ -161,13 +161,13 @@ public data class EventResults(
@get:Valid
public val changeEvents: List<Event>,
@get:JsonIgnore
public val properties: MutableMap<String, Any> = mutableMapOf(),
public val properties: MutableMap<String, Any?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Any> = properties
public fun `get`(): Map<String, Any?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Any) {
public fun `set`(name: String, `value`: Any?) {
properties[name] = value
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ public data class ValidationAnnotations(
@param:JsonProperty("qualities")
@get:JsonProperty("qualities")
@get:Valid
public val qualities: Map<String, QualitiesValue>? = null,
public val qualities: Map<String, QualitiesValue?>? = null,
)
55 changes: 28 additions & 27 deletions src/test/resources/examples/mapExamples/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public data class ComplexObjectWithMapsOfMaps(
@param:JsonProperty("map-of-maps")
@get:JsonProperty("map-of-maps")
@get:Valid
public val mapOfMaps: Map<String, Map<String, BasicObject>>? = null,
public val mapOfMaps: Map<String, Map<String, BasicObject?>?>? = null,
)

public data class ComplexObjectWithRefTypedMap(
Expand All @@ -37,13 +37,13 @@ public data class ComplexObjectWithRefTypedMap(
@get:JsonProperty("code")
public val code: Int? = null,
@get:JsonIgnore
public val properties: MutableMap<String, SomeRef> = mutableMapOf(),
public val properties: MutableMap<String, SomeRef?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, SomeRef> = properties
public fun `get`(): Map<String, SomeRef?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: SomeRef) {
public fun `set`(name: String, `value`: SomeRef?) {
properties[name] = value
}
}
Expand All @@ -56,13 +56,13 @@ public data class ComplexObjectWithTypedMap(
@get:JsonProperty("code")
public val code: Int? = null,
@get:JsonIgnore
public val properties: MutableMap<String, ComplexObjectWithTypedMapValue> = mutableMapOf(),
public val properties: MutableMap<String, ComplexObjectWithTypedMapValue?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, ComplexObjectWithTypedMapValue> = properties
public fun `get`(): Map<String, ComplexObjectWithTypedMapValue?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: ComplexObjectWithTypedMapValue) {
public fun `set`(name: String, `value`: ComplexObjectWithTypedMapValue?) {
properties[name] = value
}
}
Expand All @@ -84,13 +84,13 @@ public data class ComplexObjectWithUntypedMap(
@get:JsonProperty("code")
public val code: Int? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Any> = mutableMapOf(),
public val properties: MutableMap<String, Any?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Any> = properties
public fun `get`(): Map<String, Any?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Any) {
public fun `set`(name: String, `value`: Any?) {
properties[name] = value
}
}
Expand Down Expand Up @@ -122,30 +122,30 @@ public data class InlinedTypedObjectMapValue(
public data class MapHolder(
@param:JsonProperty("wild_card")
@get:JsonProperty("wild_card")
public val wildCard: Map<String, Any>? = null,
public val wildCard: Map<String, Any?>? = null,
@param:JsonProperty("string_map")
@get:JsonProperty("string_map")
public val stringMap: Map<String, String>? = null,
public val stringMap: Map<String, String?>? = null,
@param:JsonProperty("typed_object_map")
@get:JsonProperty("typed_object_map")
@get:Valid
public val typedObjectMap: Map<String, TypedObjectMapValue>? = null,
public val typedObjectMap: Map<String, TypedObjectMapValue?>? = null,
@param:JsonProperty("object_map")
@get:JsonProperty("object_map")
public val objectMap: Map<String, Map<String, Any>>? = null,
public val objectMap: Map<String, Map<String, Any?>?>? = null,
@param:JsonProperty("inlined_string_map")
@get:JsonProperty("inlined_string_map")
public val inlinedStringMap: Map<String, String>? = null,
public val inlinedStringMap: Map<String, String?>? = null,
@param:JsonProperty("inlined_object_map")
@get:JsonProperty("inlined_object_map")
public val inlinedObjectMap: Map<String, Map<String, Any>>? = null,
public val inlinedObjectMap: Map<String, Map<String, Any?>?>? = null,
@param:JsonProperty("inlined_unknown_map")
@get:JsonProperty("inlined_unknown_map")
public val inlinedUnknownMap: Map<String, Any>? = null,
public val inlinedUnknownMap: Map<String, Any?>? = null,
@param:JsonProperty("inlined_typed_object_map")
@get:JsonProperty("inlined_typed_object_map")
@get:Valid
public val inlinedTypedObjectMap: Map<String, InlinedTypedObjectMapValue>? = null,
public val inlinedTypedObjectMap: Map<String, InlinedTypedObjectMapValue?>? = null,
@param:JsonProperty("complex_object_with_untyped_map")
@get:JsonProperty("complex_object_with_untyped_map")
@get:Valid
Expand All @@ -164,13 +164,13 @@ public data class MapHolder(
@get:Valid
public val inlinedComplexObjectWithTypedMap: MapHolderInlinedComplexObjectWithTypedMap? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Map<String, ExternalObjectFour>> = mutableMapOf(),
public val properties: MutableMap<String, Map<String, ExternalObjectFour?>?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Map<String, ExternalObjectFour>> = properties
public fun `get`(): Map<String, Map<String, ExternalObjectFour?>?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Map<String, ExternalObjectFour>) {
public fun `set`(name: String, `value`: Map<String, ExternalObjectFour?>?) {
properties[name] = value
}
}
Expand All @@ -183,13 +183,14 @@ public data class MapHolderInlinedComplexObjectWithTypedMap(
@get:JsonProperty("code")
public val code: Int? = null,
@get:JsonIgnore
public val properties: MutableMap<String, InlinedComplexObjectWithTypedMapValue> = mutableMapOf(),
public val properties: MutableMap<String, InlinedComplexObjectWithTypedMapValue?> =
mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, InlinedComplexObjectWithTypedMapValue> = properties
public fun `get`(): Map<String, InlinedComplexObjectWithTypedMapValue?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: InlinedComplexObjectWithTypedMapValue) {
public fun `set`(name: String, `value`: InlinedComplexObjectWithTypedMapValue?) {
properties[name] = value
}
}
Expand All @@ -202,13 +203,13 @@ public data class MapHolderInlinedComplexObjectWithUntypedMap(
@get:JsonProperty("code")
public val code: Int? = null,
@get:JsonIgnore
public val properties: MutableMap<String, Any> = mutableMapOf(),
public val properties: MutableMap<String, Any?> = mutableMapOf(),
) {
@JsonAnyGetter
public fun `get`(): Map<String, Any> = properties
public fun `get`(): Map<String, Any?> = properties

@JsonAnySetter
public fun `set`(name: String, `value`: Any) {
public fun `set`(name: String, `value`: Any?) {
properties[name] = value
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/resources/examples/wildCardTypes/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ public data class WildCardTypes(
@param:JsonProperty("meta")
@get:JsonProperty("meta")
@get:NotNull
public val meta: Map<String, Any>,
public val meta: Map<String, Any?>,
)

0 comments on commit 05e94d8

Please sign in to comment.