Skip to content

Commit a2fd97f

Browse files
committed
remember response status on grouping by content type (openapi-processor/openapi-processor-spring#339)
1 parent 63a2108 commit a2fd97f

File tree

9 files changed

+253
-111
lines changed

9 files changed

+253
-111
lines changed

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/framework/FrameworkAnnotations.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
package io.openapiprocessor.core.framework
77

8+
import io.openapiprocessor.core.model.Annotation
9+
import io.openapiprocessor.core.model.EndpointResponseStatus
810
import io.openapiprocessor.core.model.parameters.Parameter
911
import io.openapiprocessor.core.parser.HttpMethod
10-
import io.openapiprocessor.core.model.Annotation
1112

1213
/**
1314
* provides annotation details of the framework.
@@ -29,4 +30,9 @@ interface FrameworkAnnotations {
2930
* @return annotation details
3031
*/
3132
fun getAnnotation(parameter: Parameter): Annotation
33+
34+
/**
35+
* provides the details of a response status annotation
36+
*/
37+
fun getAnnotation(status: EndpointResponseStatus): Annotation
3238
}

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/model/Endpoint.kt

Lines changed: 54 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2019 https://github.com/openapi-processor/openapi-processor-core
2+
* Copyright 2019 https://github.com/openapi-processor/openapi-processor-core
33
* PDX-License-Identifier: Apache-2.0
44
*/
55

@@ -9,6 +9,7 @@ package io.openapiprocessor.core.model
99
import io.openapiprocessor.core.model.parameters.MultipartParameter
1010
import io.openapiprocessor.core.model.parameters.Parameter
1111
import io.openapiprocessor.core.parser.HttpMethod
12+
import kotlin.collections.plusAssign
1213

1314
/**
1415
* Endpoint properties.
@@ -38,11 +39,12 @@ class Endpoint(
3839
* @return the list of responses
3940
*/
4041
@Deprecated("only used in class")
41-
private fun getResponses (status: String): List<Response> {
42+
private fun getResponses (status: String): List<ResponseWithStatus> {
4243
if (!responses.containsKey (status)) {
4344
return emptyList()
4445
}
4546
return responses.getOrDefault(status, emptyList())
47+
.map { ResponseWithStatus(status, it) }
4648
}
4749

4850
/**
@@ -72,30 +74,16 @@ class Endpoint(
7274
return contentTypes
7375
}
7476

75-
// not needed.... => EndpointResponse.getContentTypes()
7677
@Deprecated("only used in test")
77-
fun getProducesContentTypes (status: String): List<String> {
78-
val responses = getResponses (status)
79-
val errors = getErrorResponses ()
80-
81-
val contentTypes = mutableSetOf<String>()
82-
responses.forEach {
83-
if (it.empty) {
84-
return@forEach
85-
}
86-
87-
contentTypes.add (it.contentType)
88-
}
89-
90-
errors.forEach {
91-
contentTypes.add (it.contentType)
92-
}
93-
94-
return contentTypes.toList ()
78+
fun getProducesContentTypes (status: String): Set<String> {
79+
return endpointResponses
80+
.map { it.contentTypes }
81+
.flatten()
82+
.toSet()
9583
}
9684

9785
/**
98-
* test support => extension function ?
86+
* test support => extension function?
9987
*
10088
* @param status the response status
10189
* @return first response of status
@@ -124,7 +112,7 @@ class Endpoint(
124112
/**
125113
* checks if the endpoint has multiple success responses with different content types.
126114
*
127-
* @return true if condition is met, otherwise false.
115+
* @return true if the condition is met, otherwise false.
128116
*/
129117
fun hasMultipleEndpointResponses(): Boolean {
130118
return endpointResponses.size > 1
@@ -134,7 +122,7 @@ class Endpoint(
134122
* creates groups from the responses.
135123
*
136124
* if the endpoint does provide its result in multiple content types it will create one entry
137-
* for each response kind (main response). if error responses are defined they are added as
125+
* for each response kind (main response). if error responses are defined, they are added as
138126
* error responses.
139127
*
140128
* this is used to create one controller method for each (successful) response definition.
@@ -145,56 +133,65 @@ class Endpoint(
145133
val successes = getSuccessResponses()
146134
val errors = getErrorResponses()
147135

148-
return successes.map { (key, responses) ->
149-
EndpointResponse(responses.first(), errors, responses)
136+
return successes.map { (_, statusResponses) ->
137+
EndpointResponse(statusResponses.first(), errors, statusResponses)
150138
}
151139
}
152140

153-
private fun getSuccessResponses(): MutableMap<ContentType, MutableList<Response>> {
154-
val result = mutableMapOf<ContentType, MutableList<Response>>()
155-
156-
// prefer responses with content type.
157-
filterSuccessResponses()
158-
.filter { hasContentType(it) }
159-
.forEach {
160-
var contentValues = result[it.contentType]
161-
if (contentValues == null) {
162-
contentValues = mutableListOf()
163-
result[it.contentType] = contentValues
164-
}
165-
contentValues += it
141+
private fun getSuccessResponses(): MutableMap<ContentType, MutableList<ResponseWithStatus>> {
142+
val result = mutableMapOf<ContentType, MutableList<ResponseWithStatus>>()
143+
144+
responses
145+
.filterKeys { isSuccessCode(it) }
146+
.forEach { entry ->
147+
val status = entry.key
148+
149+
entry.value
150+
.filter { hasContentType(it) }
151+
.forEach { response ->
152+
var contentValues = result[response.contentType]
153+
if (contentValues == null) {
154+
contentValues = mutableListOf()
155+
result[response.contentType] = contentValues
156+
}
157+
contentValues += ResponseWithStatus(status, response)
158+
}
166159
}
167160

168-
// check for responses without content type (e.g. 204) to generate a void method.
161+
// check for responses without a content type (e.g., 204) to generate a void method.
169162
if (result.isEmpty()) {
170-
filterSuccessResponses()
171-
.forEach {
172-
var contentValues = result[it.contentType]
173-
if (contentValues == null) {
174-
contentValues = mutableListOf()
175-
result[it.contentType] = contentValues
176-
}
177-
contentValues += it
163+
responses
164+
.filterKeys { isSuccessCode(it) }
165+
.forEach { entry ->
166+
val status = entry.key
167+
168+
entry.value
169+
.forEach { response ->
170+
var contentValues = result[response.contentType]
171+
if (contentValues == null) {
172+
contentValues = mutableListOf()
173+
result[response.contentType] = contentValues
174+
}
175+
contentValues += ResponseWithStatus(status, response)
176+
}
178177
}
179178
}
180179

181180
return result
182181
}
183182

184-
private fun getErrorResponses(): Set<Response> {
183+
private fun getErrorResponses(): Set<ResponseWithStatus> {
185184
return responses
186185
.filterKeys { !isSuccessCode(it) }
187-
.values
188-
.map { it.first() }
189-
.filter { !it.empty }
186+
.map { entry ->
187+
val status = entry.key
188+
val response = entry.value.first()
189+
ResponseWithStatus(status, response)
190+
}
191+
.filter { !it.response.empty }
190192
.toSet()
191193
}
192194

193-
private fun filterSuccessResponses() = responses
194-
.filterKeys { isSuccessCode(it) }
195-
.values
196-
.flatten()
197-
198195
private fun isSuccessCode(code: String) = code.startsWith("2")
199196

200197
private fun hasContentType(response: Response) = response.contentType != "?"

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/model/EndpointResponse.kt

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,54 @@ import io.openapiprocessor.core.processor.mapping.v2.ResultStyle
1313
* The responses that can be returned by an endpoint method for one (successful) response.
1414
*/
1515
class EndpointResponse(
16-
1716
/**
1817
* success response
1918
*/
20-
private val main: Response,
19+
@Deprecated("use successes", replaceWith = ReplaceWith("successes"))
20+
private val main: ResponseWithStatus,
2121

2222
/**
2323
* additional (error) responses
2424
*/
25-
private val errors: Set<Response>,
25+
private val errors: Set<ResponseWithStatus>,
2626

2727
/**
2828
* todo replace main
2929
* success responses
3030
*/
31-
private val successes: List<Response> = listOf()
32-
) {
31+
private val successes: List<ResponseWithStatus> = listOf()
32+
): EndpointResponseStatus {
33+
34+
val success: Response
35+
get() = main.response
36+
3337
// todo all have the same, maybe pass as parameter
3438
val contentType: String
35-
get() = main.contentType
39+
get() = success.contentType
40+
41+
override val statusCode: HttpStatus
42+
get() {
43+
if (successes.size == 1) {
44+
return successes.first().status
45+
}
46+
47+
if (errors.size == 1) {
48+
return errors.first().status
49+
}
50+
51+
throw IllegalStateException("multiple successful or error responses without status code")
52+
}
3653

3754
/**
38-
* provides the response type based on the requested style.
55+
* Provides the response type based on the requested style.
3956
*
4057
* [ResultStyle.SUCCESS]
41-
* - response type is the single success response type regardless of the number of available
58+
* - Response type is the single success response type regardless of the number of available
4259
* error responses.
4360
*
4461
* [ResultStyle.ALL]
4562
* - If the endpoint has multiple responses the response type is `Object`. If the response is
46-
* wrapped by a result data type (i.e. wrapper) the response type is `ResultDataType<?>`.
63+
* wrapped by a result data type (i.e., wrapper) the response type is `ResultDataType<?>`.
4764
* - If the endpoint has only a success response type it is used as the response type.
4865
*
4966
* @param style required style
@@ -63,6 +80,29 @@ class EndpointResponse(
6380
return getSingleResponseTypeName()
6481
}
6582

83+
/**
84+
* Check if the response has a single response status not equal to 200 ok. It is used to check if it is possible
85+
* to generate code that automatically returns the status code (like annotation the endpoint method).
86+
*
87+
* [ResultStyle.SUCCESS]
88+
* - True if there is a single response status not equal to 200, i.e., any success status
89+
*
90+
* [ResultStyle.ALL]
91+
* - True if there is a single response status not equal to 200, i.e., any status
92+
*/
93+
fun hasSingleResponse(style: ResultStyle): Boolean {
94+
if (style == ResultStyle.ALL) {
95+
if (successes.size == 1 && errors.isEmpty()) {
96+
return successes.first().status != "200"
97+
}
98+
} else if (style == ResultStyle.SUCCESS) {
99+
if (successes.size == 1) {
100+
return successes.first().status != "200"
101+
}
102+
}
103+
return false
104+
}
105+
66106
/**
67107
* test only: provides the response type.
68108
*/
@@ -72,7 +112,7 @@ class EndpointResponse(
72112
}
73113

74114
val description: String?
75-
get() = main.description
115+
get() = success.description
76116

77117
/**
78118
* provides the imports required for {@link #getResponseType()}.
@@ -103,25 +143,25 @@ class EndpointResponse(
103143
val contentTypes: Set<String>
104144
get() {
105145
val result = mutableSetOf<String>()
106-
if (!main.empty) {
107-
result.add(main.contentType)
146+
if (!success.empty) {
147+
result.add(success.contentType)
108148
}
109149

110150
errors.forEach {
111-
result.add(it.contentType)
151+
result.add(it.response.contentType)
112152
}
113153
return result
114154
}
115155

116156
private fun isAnyOneOfResponse(): Boolean {
117-
return main.responseType is AnyOneOfObjectDataType
157+
return success.responseType is AnyOneOfObjectDataType
118158
}
119159

120160
/**
121161
* Object or ResultDataType<?> if wrapped
122162
*/
123163
private fun getMultiResponseTypeName(): String {
124-
val rt = main.responseType
164+
val rt = success.responseType
125165
if (rt is ResultDataType) {
126166
return rt.getNameMulti()
127167
}
@@ -130,7 +170,7 @@ class EndpointResponse(
130170

131171
private fun getSingleResponseTypeName(): String {
132172
val types = getDistinctResponseTypes()
133-
.map { r -> r.responseType.getTypeName() }
173+
.map { r -> r.response.responseType.getTypeName() }
134174

135175
if(types.size != 1) {
136176
throw IllegalStateException("ambiguous response types: $types")
@@ -140,7 +180,7 @@ class EndpointResponse(
140180
}
141181

142182
private fun getImportsMulti(): Set<String> {
143-
val rt = main.responseType
183+
val rt = success.responseType
144184
return if (rt is ResultDataType) {
145185
rt.getImportsMulti()
146186
} else {
@@ -149,10 +189,10 @@ class EndpointResponse(
149189
}
150190

151191
private fun getImportsSingle(): Set<String> {
152-
return main.imports
192+
return success.imports
153193
}
154194

155-
private fun getDistinctResponseTypes(): List<Response> {
156-
return successes.distinctBy { response -> response.responseType.getTypeName() }
195+
private fun getDistinctResponseTypes(): List<ResponseWithStatus> {
196+
return successes.distinctBy { statusResponse -> statusResponse.response.responseType.getTypeName() }
157197
}
158198
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2015 https://github.com/openapi-processor/openapi-processor-core
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.model
7+
8+
interface EndpointResponseStatus {
9+
val statusCode: HttpStatus
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2015 https://github.com/openapi-processor/openapi-processor-core
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.model
7+
8+
data class ResponseWithStatus(val status: HttpStatus, val response: Response)

openapi-processor-core/src/test/groovy/io/openapiprocessor/core/model/EndpointContentTypesSpec.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class EndpointContentTypesSpec extends Specification {
6767
}
6868

6969
expect:
70-
endpoint.getProducesContentTypes ('204') == []
70+
endpoint.getProducesContentTypes ('204') == [] as Set
7171
}
7272

7373
void "provides producing content types" () {

0 commit comments

Comments
 (0)