Skip to content

Commit

Permalink
Feature/87 support enums in generators (#130)
Browse files Browse the repository at this point in the history
* Add type property to enum schemas in generated JSON schema.

* Add test to ensure that enum values are generated in OpenApi20Generator.

* Add test to ensure that enum values are generated in OpenApi3Generator.

* Fixed lint errors.

* Fixed nondeterministic test failure.

* Use CombinedSchema to set type property for Enum schemas.
  • Loading branch information
Thomas Wimmer authored Feb 27, 2020
1 parent 21213ef commit d68049a
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ class JsonSchemaFromFieldDescriptorsGenerator {
"string" -> StringSchema.builder()
.minLength(minLengthString(this))
.maxLength(maxLengthString(this))
"enum" -> EnumSchema.builder().possibleValues(this.attributes.enumValues)
"enum" -> CombinedSchema.oneOf(
listOf(
StringSchema.builder().build(),
EnumSchema.builder().possibleValues(this.attributes.enumValues).build())
).isSynthetic(true)
else -> throw IllegalArgumentException("unknown field type $type")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.everit.json.schema.ArraySchema
import org.everit.json.schema.ObjectSchema
import org.everit.json.schema.Schema
import org.everit.json.schema.StringSchema
import org.everit.json.schema.CombinedSchema
import org.everit.json.schema.EnumSchema
import org.everit.json.schema.ValidationException
import org.everit.json.schema.loader.SchemaLoader
Expand Down Expand Up @@ -203,7 +204,14 @@ class JsonSchemaFromFieldDescriptorsGeneratorTest {

then(schema).isInstanceOf(ObjectSchema::class.java)
val objectSchema = schema as ObjectSchema?
then(objectSchema!!.propertySchemas["some"]).isInstanceOf(EnumSchema::class.java)

val enumSchema = objectSchema!!.propertySchemas["some"]
then(enumSchema).isInstanceOf(CombinedSchema::class.java)

val subschemas = (enumSchema as CombinedSchema).subschemas.toList()
then(subschemas).hasSize(2)
then(subschemas).extracting("class").containsOnlyOnce(EnumSchema::class.java)
then(subschemas).extracting("class").containsOnlyOnce(StringSchema::class.java)

thenSchemaIsValid()
thenSchemaValidatesJson("""{ some: "ENUM_VALUE_1" }""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.epages.restdocs.apispec.model.SecurityRequirements
import com.epages.restdocs.apispec.model.Schema
import com.epages.restdocs.apispec.model.SecurityType.BASIC
import com.epages.restdocs.apispec.model.SecurityType.OAUTH2
import com.epages.restdocs.apispec.model.Attributes
import com.fasterxml.jackson.module.kotlin.readValue
import io.swagger.models.Model
import io.swagger.models.Path
Expand All @@ -24,6 +25,7 @@ import io.swagger.models.auth.OAuth2Definition
import io.swagger.models.parameters.BodyParameter
import io.swagger.models.parameters.Parameter
import io.swagger.models.parameters.PathParameter
import io.swagger.models.properties.StringProperty
import io.swagger.parser.Swagger20Parser
import io.swagger.util.Json
import org.assertj.core.api.Assertions.tuple
Expand Down Expand Up @@ -209,6 +211,16 @@ class OpenApi20GeneratorTest {
thenValidateOpenApi(openapi)
}

@Test
fun `should include enum values in schemas`() {
val api = givenPostProductResourceModelWithCustomSchemaNames()

val openapi = whenOpenApiObjectGenerated(api)

thenEnumValuesAreSetInRequestAndResponse(openapi)
thenValidateOpenApi(openapi)
}

private fun whenExtractOrFindSchema(schemaNameAndSchemaMap: MutableMap<Model, String>, ordersSchema: Model, shopsSchema: Model) {
OpenApi20Generator.extractOrFindSchema(schemaNameAndSchemaMap, ordersSchema, OpenApi20Generator.generateSchemaName("/orders"))
OpenApi20Generator.extractOrFindSchema(schemaNameAndSchemaMap, shopsSchema, OpenApi20Generator.generateSchemaName("/shops"))
Expand Down Expand Up @@ -327,11 +339,11 @@ class OpenApi20GeneratorTest {
val requestSchemaRef = requestBody.schema.reference
then(requestSchemaRef).startsWith("${SCHEMA_JSONPATH_PREFIX}products")
val requestSchemaRefName = requestSchemaRef.replace(SCHEMA_JSONPATH_PREFIX, "")
then(definitions.get(requestSchemaRefName)!!.properties.keys).containsExactlyInAnyOrder("description", "price")
then(definitions.get(requestSchemaRefName)!!.properties.keys).containsExactlyInAnyOrder("description", "price", "someEnum")

then(successfulPostResponse.responseSchema.reference).startsWith("${SCHEMA_JSONPATH_PREFIX}products")
val responseSchemaRefName = successfulPostResponse.responseSchema.reference.replace(SCHEMA_JSONPATH_PREFIX, "")
then(definitions.get(responseSchemaRefName)!!.properties.keys).containsExactlyInAnyOrder("_id", "description", "price")
then(definitions.get(responseSchemaRefName)!!.properties.keys).containsExactlyInAnyOrder("_id", "description", "price", "someEnum")
}

private fun thenGetProductWith400ResponseIsGenerated(openapi: Swagger, api: List<ResourceModel>) {
Expand Down Expand Up @@ -392,6 +404,13 @@ class OpenApi20GeneratorTest {
then(openapi.definitions.keys).contains("ProductResponse2")
}

private fun thenEnumValuesAreSetInRequestAndResponse(openapi: Swagger) {
then(openapi.definitions["ProductRequest"]?.properties?.keys ?: emptyList()).contains("someEnum")
then((openapi.definitions["ProductRequest"]!!.properties["someEnum"] as StringProperty).enum).containsExactly("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE")
then(openapi.definitions["ProductResponse"]?.properties?.keys ?: emptyList()).contains("someEnum")
then((openapi.definitions["ProductResponse"]!!.properties["someEnum"] as StringProperty).enum).containsExactly("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE")
}

private fun givenGetProductResourceModel(): List<ResourceModel> {
return listOf(
ResourceModel(
Expand Down Expand Up @@ -613,6 +632,12 @@ class OpenApi20GeneratorTest {
path = "price.amount",
description = "Product price.",
type = "NUMBER"
),
FieldDescriptor(
path = "someEnum",
description = "Some enum description",
type = "enum",
attributes = Attributes(enumValues = listOf("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE"))
)
),
example = example
Expand Down Expand Up @@ -798,6 +823,12 @@ class OpenApi20GeneratorTest {
path = "price.amount",
description = "Product price.",
type = "NUMBER"
),
FieldDescriptor(
path = "someEnum",
description = "Some enum description",
type = "enum",
attributes = Attributes(enumValues = listOf("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE"))
)
),
example = getProductPayloadExample()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.epages.restdocs.apispec.model.ResponseModel
import com.epages.restdocs.apispec.model.SecurityRequirements
import com.epages.restdocs.apispec.model.SecurityType
import com.epages.restdocs.apispec.model.Schema
import com.epages.restdocs.apispec.model.Attributes
import com.jayway.jsonpath.Configuration
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
Expand Down Expand Up @@ -166,6 +167,16 @@ class OpenApi3GeneratorTest {
thenOpenApiSpecIsValid()
}

@Test
fun `should include enum values in schemas`() {
givenPatchProductResourceModelWithCustomSchemaNames()

whenOpenApiObjectGenerated()

thenEnumValuesAreSetInRequestAndResponse()
thenOpenApiSpecIsValid()
}

fun thenGetProductByIdOperationIsValid() {
val productGetByIdPath = "paths./products/{id}.get"
then(openApiJsonPathContext.read<List<String>>("$productGetByIdPath.tags")).isNotNull()
Expand Down Expand Up @@ -244,6 +255,14 @@ class OpenApi3GeneratorTest {
then(schemas.keys).contains("ProductResponse2")
}

private fun thenEnumValuesAreSetInRequestAndResponse() {
val requestEnum = openApiJsonPathContext.read<Map<String, Any>>("components.schemas.ProductRequest.properties.someEnum")
then(requestEnum["enum"] as List<*>).containsExactly("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE")

val responseEnum = openApiJsonPathContext.read<Map<String, Any>>("components.schemas.ProductResponse.properties.someEnum")
then(responseEnum["enum"] as List<*>).containsExactly("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE")
}

private fun whenOpenApiObjectGenerated() {
openApiSpecJsonString = OpenApi3Generator.generateAndSerialize(
resources = resources,
Expand Down Expand Up @@ -560,6 +579,12 @@ class OpenApi3GeneratorTest {
path = "description",
description = "Product description, localized.",
type = "STRING"
),
FieldDescriptor(
path = "someEnum",
description = "Some enum description",
type = "enum",
attributes = Attributes(enumValues = listOf("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE"))
)
),
example = """{
Expand Down Expand Up @@ -610,6 +635,12 @@ class OpenApi3GeneratorTest {
path = "description1",
description = "Product description, localized.",
type = "STRING"
),
FieldDescriptor(
path = "someEnum",
description = "Some enum description",
type = "enum",
attributes = Attributes(enumValues = listOf("FIRST_VALUE", "SECOND_VALUE", "THIRD_VALUE"))
)
),
contentType = "application/json",
Expand Down

0 comments on commit d68049a

Please sign in to comment.