Skip to content

Commit

Permalink
detect and handle optional properties
Browse files Browse the repository at this point in the history
  • Loading branch information
SMILEY4 committed Aug 3, 2024
1 parent 08974f5 commit 32075e8
Show file tree
Hide file tree
Showing 12 changed files with 513 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ data class PropertyData(
* whether the (return) type is nullable
*/
var nullable: Boolean,
/**
* whether the property is optional (i.e. when a default value is provided)
*/
var optional: Boolean,
/**
* the general visibility of this property
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.smiley4.schemakenerator.core.data.Bundle
import io.github.smiley4.schemakenerator.core.data.ObjectTypeData
import io.github.smiley4.schemakenerator.core.data.PropertyData
import io.github.smiley4.schemakenerator.core.data.PropertyType
import javax.swing.text.html.HTML.Tag.U

/**
* Merges getters with their matching property;
Expand Down Expand Up @@ -35,21 +36,24 @@ class MergeGettersStep {
.filter { it.kind == PropertyType.GETTER }
.forEach { getter ->

// find matching property
val propertyName = getterNameToPropertyName(getter.name)

val property = typeData.members
.filter { it.kind == PropertyType.PROPERTY }
.find { it.name == propertyName && it.type == getter.type }

if(property != null) {
// copy some information from getter to property
property.annotations.addAll(getter.annotations)
property.nullable = getter.nullable
property.visibility = getter.visibility
} else {
// create new property from getter
toAdd.add(PropertyData(
name = propertyName,
type = getter.type,
nullable = getter.nullable,
optional = getter.optional,
visibility = getter.visibility,
kind = PropertyType.PROPERTY,
annotations = getter.annotations
Expand All @@ -58,6 +62,7 @@ class MergeGettersStep {

}

// remove all getters, add created members
typeData.members.removeIf { it.kind == PropertyType.GETTER }
typeData.members.addAll(toAdd)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,29 @@ import io.github.smiley4.schemakenerator.jsonschema.steps.JsonSchemaCoreAnnotati
import io.github.smiley4.schemakenerator.jsonschema.steps.JsonSchemaCustomizeStep
import io.github.smiley4.schemakenerator.jsonschema.steps.JsonSchemaGenerationStep

class JsonSchemaGenerationStepConfig {
/**
* Handle optional parameters as not-required
*
* Example:
* ```
* class MyExample(val someValue: String = "hello")
* ```
* - with `optionalAsNonRequired = false` => "someValue" is required (because is not nullable)
* - with `optionalAsNonRequired = true` => "someValue" is not required (because default value is provided)
*/
var optionalAsNonRequired = false
}


/**
* See [JsonSchemaGenerationStep]
*/
fun Bundle<BaseTypeData>.generateJsonSchema(): Bundle<JsonSchema> {
return JsonSchemaGenerationStep().generate(this)
fun Bundle<BaseTypeData>.generateJsonSchema(configBlock: JsonSchemaGenerationStepConfig.() -> Unit = {}): Bundle<JsonSchema> {
val config = JsonSchemaGenerationStepConfig().apply(configBlock)
return JsonSchemaGenerationStep(
optionalAsNonRequired = config.optionalAsNonRequired
).generate(this)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import io.github.smiley4.schemakenerator.jsonschema.jsonDsl.JsonNode
* Generates json-schemas from the given type data. All types in the schema are provisionally referenced by the full type-id.
* Result needs to be "compiled" to get the final json-schema.
*/
class JsonSchemaGenerationStep {
class JsonSchemaGenerationStep(private val optionalAsNonRequired: Boolean = false) {

private val schemaUtils = JsonSchemaUtils()

Expand Down Expand Up @@ -168,7 +168,9 @@ class JsonSchemaGenerationStep {

collectMembers(typeData, typeDataList).forEach { member ->
propertySchemas[member.name] = schemaUtils.referenceSchema(member.type)
if (!member.nullable) {
val nullable = member.nullable
val optional = member.optional && optionalAsNonRequired
if(!nullable && !optional) {
requiredProperties.add(member.name)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ class ReflectionTypeProcessingStep(
name = "item",
type = it.type,
nullable = it.nullable,
optional = false,
visibility = Visibility.PUBLIC,
kind = PropertyType.PROPERTY,
annotations = mutableListOf()
Expand All @@ -259,6 +260,7 @@ class ReflectionTypeProcessingStep(
name = "item",
type = it.type,
nullable = it.nullable,
optional = false,
visibility = Visibility.PUBLIC,
kind = PropertyType.PROPERTY,
annotations = mutableListOf()
Expand All @@ -268,6 +270,7 @@ class ReflectionTypeProcessingStep(
name = "item",
type = it.value.type,
nullable = it.value.nullable,
optional = false,
visibility = Visibility.PUBLIC,
kind = PropertyType.PROPERTY,
annotations = mutableListOf()
Expand All @@ -287,6 +290,7 @@ class ReflectionTypeProcessingStep(
name = "key",
type = it.type,
nullable = it.nullable,
optional = false,
visibility = Visibility.PUBLIC,
kind = PropertyType.PROPERTY,
annotations = mutableListOf()
Expand All @@ -297,6 +301,7 @@ class ReflectionTypeProcessingStep(
name = "value",
type = it.type,
nullable = it.nullable,
optional = false,
visibility = Visibility.PUBLIC,
kind = PropertyType.PROPERTY,
annotations = mutableListOf()
Expand Down Expand Up @@ -371,7 +376,7 @@ class ReflectionTypeProcessingStep(
.filter { filterMember(it) }
.mapNotNull { member ->
when (member) {
is KProperty<*> -> parseProperty(member, resolvedTypeParameters, typeData)
is KProperty<*> -> parseProperty(member, resolvedTypeParameters, typeData, clazz)
is KFunction<*> -> parseFunction(member, resolvedTypeParameters, typeData)
else -> null
}
Expand Down Expand Up @@ -432,12 +437,21 @@ class ReflectionTypeProcessingStep(
private fun parseProperty(
member: KProperty<*>,
resolvedTypeParameters: Map<String, TypeParameterData>,
typeData: MutableList<BaseTypeData>
typeData: MutableList<BaseTypeData>,
clazz: KClass<*>
): PropertyData {

val isOptional = clazz.constructors.any { constructor ->
val ctorParameter = constructor.parameters.find { parameter ->
parameter.name == member.name && parameter.type == member.returnType
}
ctorParameter?.isOptional ?: false
}
return PropertyData(
name = member.name,
type = resolveMemberType(member.returnType, resolvedTypeParameters, typeData).id,
nullable = member.returnType.isMarkedNullable,
optional = isOptional,
annotations = parseAnnotations(member).toMutableList(),
kind = PropertyType.PROPERTY,
visibility = determinePropertyVisibility(member)
Expand All @@ -453,9 +467,10 @@ class ReflectionTypeProcessingStep(
name = member.name,
type = resolveMemberType(member.returnType, resolvedTypeParameters, typeData).id,
nullable = member.returnType.isMarkedNullable,
optional = false,
annotations = parseAnnotations(member).toMutableList(),
kind = determineFunctionPropertyType(member),
visibility = determinePropertyVisibility(member)
visibility = determinePropertyVisibility(member),
)
}

Expand Down Expand Up @@ -639,6 +654,7 @@ class ReflectionTypeProcessingStep(
name = name,
type = TypeId.wildcard(),
nullable = false,
optional = false,
visibility = Visibility.PUBLIC,
kind = PropertyType.PROPERTY,
annotations = mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ class KotlinxSerializationTypeProcessingStep(
name = "item",
type = itemType.id,
nullable = itemDescriptor.isNullable,
optional = false,
kind = PropertyType.PROPERTY,
visibility = Visibility.PUBLIC,
),
Expand Down Expand Up @@ -220,6 +221,7 @@ class KotlinxSerializationTypeProcessingStep(
name = "key",
type = keyType.id,
nullable = keyDescriptor.isNullable,
optional = false,
kind = PropertyType.PROPERTY,
visibility = Visibility.PUBLIC,

Expand All @@ -228,6 +230,7 @@ class KotlinxSerializationTypeProcessingStep(
name = "value",
type = valueType.id,
nullable = valueDescriptor.isNullable,
optional = false,
kind = PropertyType.PROPERTY,
visibility = Visibility.PUBLIC,
),
Expand Down Expand Up @@ -258,6 +261,7 @@ class KotlinxSerializationTypeProcessingStep(
name = fieldName,
type = fieldType.id,
nullable = fieldDescriptor.isNullable,
optional = false,
kind = PropertyType.PROPERTY,
visibility = Visibility.PUBLIC,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,29 @@ import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaCustomizeSte
import io.github.smiley4.schemakenerator.swagger.steps.SwaggerSchemaGenerationStep
import io.swagger.v3.oas.models.media.Schema

class SwaggerSchemaGenerationStepConfig {
/**
* Handle optional parameters as not-required
*
* Example:
* ```
* class MyExample(val someValue: String = "hello")
* ```
* - with `optionalAsNonRequired = false` => "someValue" is required (because is not nullable)
* - with `optionalAsNonRequired = true` => "someValue" is not required (because default value is provided)
*/
var optionalAsNonRequired = false
}


/**
* See [SwaggerSchemaGenerationStep]
*/
fun Bundle<BaseTypeData>.generateSwaggerSchema(): Bundle<SwaggerSchema> {
return SwaggerSchemaGenerationStep().generate(this)
fun Bundle<BaseTypeData>.generateSwaggerSchema(configBlock: SwaggerSchemaGenerationStepConfig.() -> Unit = {}): Bundle<SwaggerSchema> {
val config = SwaggerSchemaGenerationStepConfig().apply(configBlock)
return SwaggerSchemaGenerationStep(
optionalAsNonRequired = config.optionalAsNonRequired
).generate(this)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.math.BigDecimal
* Generates swagger-schemas from the given type data. All types in the schema are provisionally referenced by the full type-id.
* Result needs to be "compiled" to get the final swagger-schema.
*/
class SwaggerSchemaGenerationStep {
class SwaggerSchemaGenerationStep(private val optionalAsNonRequired: Boolean = false) {

private val schema = SwaggerSchemaUtils()

Expand Down Expand Up @@ -144,7 +144,9 @@ class SwaggerSchemaGenerationStep {

collectMembers(typeData, typeDataList).forEach { member ->
propertySchemas[member.name] = schema.referenceSchema(member.type)
if (!member.nullable) {
val nullable = member.nullable
val optional = member.optional && optionalAsNonRequired
if(!nullable && !optional) {
requiredProperties.add(member.name)
}
}
Expand Down
Loading

0 comments on commit 32075e8

Please sign in to comment.