Skip to content

Commit 728f7bd

Browse files
authored
Fix not supported attributes for parameter and header descriptor (#200)
* Fix not forward attributes in `HeaderDescriptor`, `ParameterDescriptor` * Add enumValues management for `AbstractParameterDescriptor` and OA3 schema * Add enumValues management for OA2 `Path|Header|Query|Form` Parameter * Fix path and request parameter name for easier to distinguish in resource snippet test * Fix clearly request model in OA3 enum values generator test * Change `Attributes.enumValues` item type to `Any` * Update OA2 enumValues test
1 parent 4d26dfe commit 728f7bd

File tree

7 files changed

+459
-5
lines changed

7 files changed

+459
-5
lines changed

restdocs-api-spec-model/src/main/kotlin/com/epages/restdocs/apispec/model/ResourceModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ open class FieldDescriptor(
8989

9090
data class Attributes(
9191
val validationConstraints: List<Constraint> = emptyList(),
92-
val enumValues: List<String> = emptyList(),
92+
val enumValues: List<Any> = emptyList(),
9393
val itemsType: String? = null
9494
)
9595

restdocs-api-spec-openapi-generator/src/main/kotlin/com/epages/restdocs/apispec/openapi2/OpenApi20Generator.kt

+4
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ object OpenApi20Generator {
418418
description = parameterDescriptor.description
419419
type = parameterDescriptor.type.toLowerCase()
420420
default = parameterDescriptor.defaultValue
421+
enumValue = parameterDescriptor.attributes.enumValues.ifEmpty { null }
421422
}
422423
}
423424

@@ -436,6 +437,7 @@ object OpenApi20Generator {
436437
required = parameterDescriptor.optional.not()
437438
type = parameterDescriptor.type.toLowerCase()
438439
default = parameterDescriptor.defaultValue
440+
enumValue = parameterDescriptor.attributes.enumValues.ifEmpty { null }
439441
}
440442
}
441443

@@ -446,6 +448,7 @@ object OpenApi20Generator {
446448
required = parameterDescriptor.optional.not()
447449
type = parameterDescriptor.type.toLowerCase()
448450
default = parameterDescriptor.defaultValue
451+
enumValue = parameterDescriptor.attributes.enumValues.ifEmpty { null }
449452
}
450453
}
451454

@@ -456,6 +459,7 @@ object OpenApi20Generator {
456459
required = headerDescriptor.optional.not()
457460
type = headerDescriptor.type.toLowerCase()
458461
default = headerDescriptor.defaultValue
462+
enumValue = headerDescriptor.attributes.enumValues.ifEmpty { null }
459463
}
460464
}
461465

restdocs-api-spec-openapi-generator/src/test/kotlin/com/epages/restdocs/apispec/openapi2/OpenApi20GeneratorTest.kt

+114
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,16 @@ class OpenApi20GeneratorTest {
262262
thenValidateOpenApi(openapi)
263263
}
264264

265+
@Test
266+
fun `should include enum values`() {
267+
val api = givenResourcesWithEnumValues()
268+
269+
val openapi = whenOpenApiObjectGenerated(api)
270+
271+
thenGetProductWith200ResponseIsGeneratedWithEnumValues(openapi, api)
272+
thenValidateOpenApi(openapi)
273+
}
274+
265275
private fun whenExtractOrFindSchema(schemaNameAndSchemaMap: MutableMap<Model, String>, ordersSchema: Model, shopsSchema: Model) {
266276
OpenApi20Generator.extractOrFindSchema(schemaNameAndSchemaMap, ordersSchema, OpenApi20Generator.generateSchemaName("/orders"))
267277
OpenApi20Generator.extractOrFindSchema(schemaNameAndSchemaMap, shopsSchema, OpenApi20Generator.generateSchemaName("/shops"))
@@ -375,6 +385,12 @@ class OpenApi20GeneratorTest {
375385
thenParametersForGetMatchWithDefaultValue(productPath.get.parameters as List<AbstractSerializableParameter<*>>, successfulGetProductModel.request)
376386
}
377387

388+
private fun thenGetProductWith200ResponseIsGeneratedWithEnumValues(openapi: Swagger, api: List<ResourceModel>) {
389+
val successfulGetProductModel = api[0]
390+
val productPath = openapi.paths.getValue(successfulGetProductModel.request.path)
391+
thenParametersForGetMatchWithEnumValues(productPath.get.parameters as List<AbstractSerializableParameter<*>>, successfulGetProductModel.request)
392+
}
393+
378394
private fun thenPostProductWith200ResponseIsGenerated(openapi: Swagger, api: List<ResourceModel>) {
379395
val successfulPostProductModel = api[0]
380396
val productPath = openapi.paths.getValue(successfulPostProductModel.request.path)
@@ -445,6 +461,16 @@ class OpenApi20GeneratorTest {
445461
thenParameterMatches(parameters, "header", request.headers[1])
446462
}
447463

464+
private fun thenParametersForGetMatchWithEnumValues(parameters: List<AbstractSerializableParameter<*>>, request: RequestModel) {
465+
thenParameterMatches(parameters, "path", request.pathParameters[0])
466+
thenParameterEnumValuesMatches(parameters, "header", request.headers[0])
467+
thenParameterEnumValuesMatches(parameters, "header", request.headers[1])
468+
thenParameterEnumValuesMatches(parameters, "query", request.requestParameters[0])
469+
thenParameterEnumValuesMatches(parameters, "query", request.requestParameters[1])
470+
thenParameterEnumValuesMatches(parameters, "query", request.requestParameters[2])
471+
thenParameterEnumValuesMatches(parameters, "query", request.requestParameters[3])
472+
}
473+
448474
private fun thenParametersForPostMatch(parameters: List<AbstractSerializableParameter<*>>, request: RequestModel) {
449475
thenParameterMatches(parameters, "header", request.headers[0])
450476
}
@@ -456,6 +482,12 @@ class OpenApi20GeneratorTest {
456482
then(parameter!!.default).isEqualTo(parameterDescriptor.defaultValue)
457483
}
458484

485+
private fun thenParameterEnumValuesMatches(parameters: List<AbstractSerializableParameter<*>>, type: String, parameterDescriptor: AbstractParameterDescriptor) {
486+
val parameter = findParameterByTypeAndName(parameters, type, parameterDescriptor.name)
487+
then(parameter).isNotNull
488+
then(parameter!!.enumValue).isEqualTo(parameterDescriptor.attributes.enumValues)
489+
}
490+
459491
private fun findParameterByTypeAndName(parameters: List<AbstractSerializableParameter<*>>, type: String, name: String): AbstractSerializableParameter<*>? {
460492
return parameters.firstOrNull { it.`in` == type && it.name == name }
461493
}
@@ -560,6 +592,21 @@ class OpenApi20GeneratorTest {
560592
)
561593
}
562594

595+
private fun givenResourcesWithEnumValues(): List<ResourceModel> {
596+
return listOf(
597+
ResourceModel(
598+
operationId = "test",
599+
summary = "summary",
600+
description = "description",
601+
privateResource = false,
602+
deprecated = false,
603+
tags = setOf("tag1", "tag2"),
604+
request = getProductRequestWithEnumValues(),
605+
response = getProduct200Response(getProductPayloadExample())
606+
)
607+
)
608+
}
609+
563610
private fun givenPostProductResourceModelWithoutFieldDescriptors(): List<ResourceModel> {
564611
return listOf(
565612
ResourceModel(
@@ -985,6 +1032,73 @@ class OpenApi20GeneratorTest {
9851032
)
9861033
}
9871034

1035+
private fun getProductRequestWithEnumValues(): RequestModel {
1036+
return getProductRequest().copy(
1037+
headers = listOf(
1038+
HeaderDescriptor(
1039+
name = "X-SOME-STRING",
1040+
description = "a header string parameter",
1041+
type = "STRING",
1042+
optional = true,
1043+
attributes = Attributes(
1044+
enumValues = listOf("HV1", "HV2")
1045+
)
1046+
),
1047+
HeaderDescriptor(
1048+
name = "X-SOME-BOOLEAN",
1049+
description = "a header boolean parameter",
1050+
type = "BOOLEAN",
1051+
optional = true,
1052+
attributes = Attributes(
1053+
enumValues = listOf("true", "false")
1054+
)
1055+
)
1056+
),
1057+
requestParameters = listOf(
1058+
ParameterDescriptor(
1059+
name = "booleanParameter",
1060+
description = "a boolean parameter",
1061+
type = "BOOLEAN",
1062+
optional = true,
1063+
ignored = false,
1064+
attributes = Attributes(
1065+
enumValues = listOf("true", "false")
1066+
)
1067+
),
1068+
ParameterDescriptor(
1069+
name = "stringParameter",
1070+
description = "a string parameter",
1071+
type = "STRING",
1072+
optional = true,
1073+
ignored = false,
1074+
attributes = Attributes(
1075+
enumValues = listOf("PV1", "PV2", "PV3")
1076+
)
1077+
),
1078+
ParameterDescriptor(
1079+
name = "numberParameter",
1080+
description = "a number parameter",
1081+
type = "NUMBER",
1082+
optional = true,
1083+
ignored = false,
1084+
attributes = Attributes(
1085+
enumValues = listOf(0.1, 0.2, 0.3)
1086+
)
1087+
),
1088+
ParameterDescriptor(
1089+
name = "integerParameter",
1090+
description = "a integer parameter",
1091+
type = "INTEGER",
1092+
optional = true,
1093+
ignored = false,
1094+
attributes = Attributes(
1095+
enumValues = listOf(1, 2, 3)
1096+
)
1097+
)
1098+
)
1099+
)
1100+
}
1101+
9881102
private fun productRequest(schema: Schema? = null, path: String = "/products", method: HTTPMethod = HTTPMethod.POST): RequestModel {
9891103
return RequestModel(
9901104
path = path,

restdocs-api-spec-openapi3-generator/src/main/kotlin/com/epages/restdocs/apispec/openapi3/OpenApi3Generator.kt

+30-4
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,36 @@ object OpenApi3Generator {
443443

444444
private fun simpleTypeToSchema(parameterDescriptor: AbstractParameterDescriptor): Schema<*>? {
445445
return when (parameterDescriptor.type.toLowerCase()) {
446-
SimpleType.BOOLEAN.name.toLowerCase() -> BooleanSchema().apply { this._default(parameterDescriptor.defaultValue?.let { it as Boolean }) }
447-
SimpleType.STRING.name.toLowerCase() -> StringSchema().apply { this._default(parameterDescriptor.defaultValue?.let { it as String }) }
448-
SimpleType.NUMBER.name.toLowerCase() -> NumberSchema().apply { this._default(parameterDescriptor.defaultValue?.let { it as BigDecimal }) }
449-
SimpleType.INTEGER.name.toLowerCase() -> IntegerSchema().apply { this._default(parameterDescriptor.defaultValue?.let { it as Int }) }
446+
SimpleType.BOOLEAN.name.toLowerCase() -> BooleanSchema().apply {
447+
this._default(parameterDescriptor.defaultValue?.let { it as Boolean })
448+
parameterDescriptor.attributes.enumValues
449+
.map { it as Boolean }
450+
.forEach { this.addEnumItem(it) }
451+
}
452+
SimpleType.STRING.name.toLowerCase() -> StringSchema().apply {
453+
this._default(parameterDescriptor.defaultValue?.let { it as String })
454+
parameterDescriptor.attributes.enumValues
455+
.map { it as String }
456+
.forEach { this.addEnumItem(it) }
457+
}
458+
SimpleType.NUMBER.name.toLowerCase() -> NumberSchema().apply {
459+
this._default(parameterDescriptor.defaultValue?.let { it as BigDecimal })
460+
parameterDescriptor.attributes.enumValues
461+
.map {
462+
when (it) {
463+
is Int -> it.toBigDecimal()
464+
is Double -> it.toBigDecimal()
465+
else -> it as BigDecimal
466+
}
467+
}
468+
.forEach { this.addEnumItem(it) }
469+
}
470+
SimpleType.INTEGER.name.toLowerCase() -> IntegerSchema().apply {
471+
this._default(parameterDescriptor.defaultValue?.let { it as Int })
472+
parameterDescriptor.attributes.enumValues
473+
.map { it as Int }
474+
.forEach { this.addEnumItem(it) }
475+
}
450476
else -> throw IllegalArgumentException("Unknown type '${parameterDescriptor.type}'")
451477
}
452478
}

0 commit comments

Comments
 (0)