Skip to content

Commit 93d0506

Browse files
committed
extract response collector (#247)
1 parent f43a8d8 commit 93d0506

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2025 https://github.com/openapi-processor/openapi-processor-base
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.model
7+
8+
import io.openapiprocessor.core.parser.ContentType
9+
import io.openapiprocessor.core.parser.HttpStatus
10+
import io.openapiprocessor.core.parser.Response
11+
import io.openapiprocessor.core.processor.mapping.v2.ResultStyle
12+
13+
14+
private const val EMPTY: String = ""
15+
16+
17+
/**
18+
* collect responses by content type to filter & select the final response data type.
19+
*
20+
* multiple & all => Object if different result types
21+
* multiple & success => Marker if different result types
22+
*
23+
* content type => success 2x
24+
* errors 4x
25+
*
26+
* content type => success 2x Result A => marker interface or Object (depends on result style)
27+
* success 2x Result B => marker interface or Object (depends on result style)
28+
* errors 4x
29+
*
30+
* content type => success 2x Result A
31+
* success 2x void
32+
* errors 4x
33+
*/
34+
class EndpointResponseCollector(val responses: Map<HttpStatus, Response>, private val resultStyle: ResultStyle) {
35+
val contentTypeResponses: Map<ContentType, Map<HttpStatus, Response>>
36+
37+
init {
38+
contentTypeResponses = collect(responses)
39+
}
40+
41+
private fun collect(responses: Map<HttpStatus, Response>): Map<ContentType, Map<HttpStatus, Response>> {
42+
val contentTypeResponses = mutableMapOf<ContentType, MutableMap<HttpStatus, Response>>()
43+
44+
responses.forEach { (httpStatus, response) ->
45+
val contents = response.getContent()
46+
47+
if (resultStyle == ResultStyle.SUCCESS && isError(httpStatus)) {
48+
return@forEach
49+
}
50+
51+
contents.keys.forEach { contentType ->
52+
var srs = contentTypeResponses[contentType]
53+
if (srs == null) {
54+
srs = mutableMapOf()
55+
contentTypeResponses[contentType] = srs
56+
}
57+
58+
srs[httpStatus] = response
59+
}
60+
61+
// no result
62+
if (contents.isEmpty()) {
63+
var srs = contentTypeResponses[EMPTY]
64+
if (srs == null) {
65+
srs = mutableMapOf()
66+
contentTypeResponses[EMPTY] = srs
67+
}
68+
69+
srs[httpStatus] = response
70+
}
71+
}
72+
73+
return contentTypeResponses
74+
}
75+
76+
77+
private fun isError(status: HttpStatus): Boolean {
78+
return status.startsWith("4")
79+
|| status.startsWith("5")
80+
}
81+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright 2025 https://github.com/openapi-processor/openapi-processor-base
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.model
7+
8+
import io.kotest.core.spec.style.StringSpec
9+
import io.kotest.matchers.nulls.shouldBeNull
10+
import io.kotest.matchers.nulls.shouldNotBeNull
11+
import io.openapiprocessor.core.processor.mapping.v2.ResultStyle
12+
import io.openapiprocessor.core.support.parseApi
13+
14+
class EndpointResponseCollectorSpec: StringSpec({
15+
16+
"single empty success response" {
17+
val openApi = parseApi(
18+
"""
19+
|openapi: 3.1.0
20+
|info:
21+
| title: responses
22+
| version: 1.0.0
23+
|paths:
24+
| /foo:
25+
| get:
26+
| responses:
27+
| '204':
28+
| description: empty
29+
""".trimMargin())
30+
31+
val operation = openApi.getPaths()["/foo"]!!.getOperations().first()
32+
33+
val collector = EndpointResponseCollector(operation.getResponses(), ResultStyle.SUCCESS)
34+
35+
collector.contentTypeResponses[""]!!["204"].shouldNotBeNull()
36+
}
37+
38+
"single success response" {
39+
val openApi = parseApi(
40+
"""
41+
|openapi: 3.1.0
42+
|info:
43+
| title: responses
44+
| version: 1.0.0
45+
|paths:
46+
| /foo:
47+
| get:
48+
| responses:
49+
| '200':
50+
| description: responses
51+
| content:
52+
| application/json:
53+
| schema:
54+
| type: string
55+
""".trimMargin())
56+
57+
val operation = openApi.getPaths()["/foo"]!!.getOperations().first()
58+
59+
val collector = EndpointResponseCollector(operation.getResponses(), ResultStyle.SUCCESS)
60+
61+
collector.contentTypeResponses["application/json"]!!["200"].shouldNotBeNull()
62+
}
63+
64+
// same result, no need to create interface, has to know if types are identical
65+
// "light" call to data type converter ?
66+
// ask if it is the same type? could use own DataTypes object???? might work...
67+
"multiple success responses without marker interface" {
68+
val openApi = parseApi(
69+
"""
70+
|openapi: 3.1.0
71+
|info:
72+
| title: responses
73+
| version: 1.0.0
74+
|paths:
75+
| /foo:
76+
| get:
77+
| responses:
78+
| '200':
79+
| description: ok
80+
| content:
81+
| application/json:
82+
| schema:
83+
| type: string
84+
| '201':
85+
| description: created
86+
| content:
87+
| application/json:
88+
| schema:
89+
| type: string
90+
""".trimMargin())
91+
92+
val operation = openApi.getPaths()["/foo"]!!.getOperations().first()
93+
94+
val collector = EndpointResponseCollector(operation.getResponses(), ResultStyle.SUCCESS)
95+
96+
collector.contentTypeResponses["application/json"]!!["200"].shouldNotBeNull()
97+
collector.contentTypeResponses["application/json"]!!["201"].shouldNotBeNull()
98+
}
99+
100+
"multiple success responses with marker interface" {
101+
val openApi = parseApi(
102+
"""
103+
|openapi: 3.1.0
104+
|info:
105+
| title: responses
106+
| version: 1.0.0
107+
|paths:
108+
| /foo:
109+
| get:
110+
| responses:
111+
| '200':
112+
| description: ok
113+
| content:
114+
| application/json:
115+
| schema:
116+
| type: string
117+
| format: one
118+
| '201':
119+
| description: created
120+
| content:
121+
| application/json:
122+
| schema:
123+
| type: string
124+
| format: two
125+
""".trimMargin())
126+
127+
val operation = openApi.getPaths()["/foo"]!!.getOperations().first()
128+
129+
val collector = EndpointResponseCollector(operation.getResponses(), ResultStyle.SUCCESS)
130+
131+
collector.contentTypeResponses["application/json"]!!["200"].shouldNotBeNull()
132+
collector.contentTypeResponses["application/json"]!!["201"].shouldNotBeNull()
133+
}
134+
135+
"multiple content type success responses with marker interface and error" {
136+
val openApi = parseApi(
137+
"""
138+
|openapi: 3.1.0
139+
|info:
140+
| title: responses
141+
| version: 1.0.0
142+
|paths:
143+
| /foo:
144+
| get:
145+
| responses:
146+
| '200':
147+
| description: ok
148+
| content:
149+
| application/json:
150+
| schema:
151+
| type: string
152+
| format: one
153+
| text/plain:
154+
| schema:
155+
| type: string
156+
| '201':
157+
| description: created
158+
| content:
159+
| application/json:
160+
| schema:
161+
| type: string
162+
| format: two
163+
| '400':
164+
| content:
165+
| application/json:
166+
| schema:
167+
| type: string
168+
| format: error
169+
|
170+
""".trimMargin())
171+
172+
val operation = openApi.getPaths()["/foo"]!!.getOperations().first()
173+
174+
val collectorA = EndpointResponseCollector(operation.getResponses(), ResultStyle.ALL)
175+
collectorA.contentTypeResponses["application/json"]!!["200"].shouldNotBeNull()
176+
collectorA.contentTypeResponses["application/json"]!!["201"].shouldNotBeNull()
177+
collectorA.contentTypeResponses["application/json"]!!["400"].shouldNotBeNull()
178+
179+
val collectorS = EndpointResponseCollector(operation.getResponses(), ResultStyle.SUCCESS)
180+
collectorS.contentTypeResponses["application/json"]!!["200"].shouldNotBeNull()
181+
collectorS.contentTypeResponses["application/json"]!!["201"].shouldNotBeNull()
182+
collectorS.contentTypeResponses["application/json"]!!["400"].shouldBeNull()
183+
}
184+
})

0 commit comments

Comments
 (0)