Commit 504091b
authored
codegen: Disambiguate when shared response model shared between multiple codes in same group, and handle 'default' error codes (#5370)
Closing #5284 and
#5286
Re-uses the same 'wrapper' declarations we use for response headers, to
handle the case where two status codes for the same method declare the
same response body. This approach _does_ mean that there's a bunch of
`sealed traits`s that get created for such endpoints, but they all
inherit from the same parent `StatusCodeDisambig`. A given code is
represented by a single object which inherits from the trait for each
endpoint that can return that code. This permits some generic handling
of errors.
Example generated code:
```scala
sealed trait StatusCodeDisambig
sealed trait DeleteInlineSimpleObjectResponseCode extends StatusCodeDisambig
sealed trait DeleteInlineSimpleObjectResponseErrCode extends StatusCodeDisambig
case object StatusCodeDisambig200
extends DeleteInlineSimpleObjectResponseCode
case object StatusCodeDisambig201
extends DeleteInlineSimpleObjectResponseCode
case object StatusCodeDisambig400
extends PutInlineSimpleObjectResponseErrCode
case object StatusCodeDisambig401
extends DeleteInlineSimpleObjectResponseErrCode
with PutInlineSimpleObjectResponseErrCode
case object StatusCodeDisambig402
extends DeleteInlineSimpleObjectResponseErrCode
type DeleteInlineSimpleObjectEndpoint = Endpoint[Unit, Unit, DeleteInlineSimpleObjectResponseErrCode, DeleteInlineSimpleObjectResponseCode, Any]
lazy val deleteInlineSimpleObject: DeleteInlineSimpleObjectEndpoint =
endpoint
.delete
.in(("inline" / "simple" / "object"))
.errorOut(oneOf[DeleteInlineSimpleObjectResponseErrCode](
oneOfVariantValueMatcher(sttp.model.StatusCode(401), emptyOutput.description("empty response 3").and(emptyOutputAs(StatusCodeDisambig401))){ case StatusCodeDisambig401 => true},
oneOfVariantValueMatcher(sttp.model.StatusCode(402), emptyOutput.description("empty response 4").and(emptyOutputAs(StatusCodeDisambig402))){ case StatusCodeDisambig402 => true}))
.out(oneOf[DeleteInlineSimpleObjectResponseCode](
oneOfVariantValueMatcher(sttp.model.StatusCode(200), emptyOutput.description("empty response 1").and(emptyOutputAs(StatusCodeDisambig200))){ case StatusCodeDisambig200 => true},
oneOfVariantValueMatcher(sttp.model.StatusCode(201), emptyOutput.description("empty response 2").and(emptyOutputAs(StatusCodeDisambig201))){ case StatusCodeDisambig201 => true}))
```
for an endpoint declared as
```yaml
'/inline/simple/object':
delete:
responses:
"200":
description: empty response 1
"201":
description: empty response 2
"401":
description: empty response 3
"402":
description: empty response 4
```
This handles 'default' codes by mapping to a case class wrapper rather
than an object. So, for
```yaml
...
responses:
"204":
description: "No response"
"404":
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/NotFoundError'
default:
description: Generic error
content:
application/json:
schema:
$ref: '#/components/schemas/SimpleError'
```
you get
```scala
...
case class StatusCodeDisambigDefault(code: sttp.model.StatusCode)
extends GetOneofErrorSecParamTestResponseErrCode
...
.errorOut(oneOf[(Error, GetOneofErrorSecParamTestResponseErrCode)](
oneOfVariantValueMatcher(sttp.model.StatusCode(404), jsonBody[NotFoundError].description("Not found").and(emptyOutputAs(StatusCodeDisambig404))){ case (_: NotFoundError, StatusCodeDisambig404) => true },
oneOfVariantValueMatcher(jsonBody[SimpleError].description("Generic error").and(statusCode.map(StatusCodeDisambigDefault(_))(_.code))){ case (_: SimpleError, (_: StatusCodeDisambigDefault)) => true }))
.out(statusCode(sttp.model.StatusCode(204)).description("No response"))
```
(if no other 'competing' error codes, a default is more simply
reducable, and ends up as just e.g.
`.errorOut(jsonBody[SimpleError].description("Generic
error").and(statusCode))`, but is still included as a param in the error
param type)
which I think is pretty good? I really wanted to explicitly avoid
passing around a raw status code since it wasn't type-safe in terms of
the status codes that can be represented on an endpoint... The shared
parent trait and common 'tuple companions' make generic handling
relatively nice, from what I've tried.
Added a behaviour flag with default false so as to not break existing
code.
One issue with this is that if you had, say, 400, 401 and 403, and the
400 had a different response type, you'd still have that extra status
code param for the 400 response, even though we could disambiguate
without it. Also you can cause a match error at runtime by specifying a
status code valid for a response of a different type. But it's a real
pain to codegen something that'll work better here, because you still
need that unified parent type -- so you'd need to introduce proper
wrappers, rather than just tupling it... Since by far the most common
use-case for this functionality is gonna be error responses, all the
real specs I've seen that don't already have different json schemas for
different error status codes use the same model for all error codes, and
since generic handling becomes a bit more of a faff with wrappers rather
than pairs, I think this still works out pretty well, so I'm happy
enough with it.1 parent e89d83d commit 504091b
24 files changed
Lines changed: 722 additions & 306 deletions
File tree
- doc/generator
- openapi-codegen
- core/src
- main/scala/sttp/tapir/codegen
- endpoints
- test
- scala-2/sttp/tapir/codegen/testutils
- scala-3/sttp/tapir/codegen/testutils
- scala/sttp/tapir/codegen
- sbt-plugin/src
- main/scala/sttp/tapir/sbt
- sbt-test/sbt-openapi-codegen
- oneOf-json-roundtrip-zio
- oneOf-json-roundtrip_jsoniter
- src/test/scala
- oneOf-json-roundtrip_scala3
- oneOf-json-roundtrip
- petstore
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
| 62 | + | |
61 | 63 | | |
62 | 64 | | |
63 | 65 | | |
| |||
Lines changed: 5 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
| 40 | + | |
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
| |||
83 | 84 | | |
84 | 85 | | |
85 | 86 | | |
86 | | - | |
| 87 | + | |
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
| |||
96 | 97 | | |
97 | 98 | | |
98 | 99 | | |
99 | | - | |
| 100 | + | |
| 101 | + | |
100 | 102 | | |
101 | 103 | | |
102 | 104 | | |
| |||
Lines changed: 31 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
38 | | - | |
| 39 | + | |
| 40 | + | |
39 | 41 | | |
40 | 42 | | |
41 | 43 | | |
42 | 44 | | |
43 | 45 | | |
44 | | - | |
| 46 | + | |
| 47 | + | |
45 | 48 | | |
46 | 49 | | |
47 | 50 | | |
| |||
86 | 89 | | |
87 | 90 | | |
88 | 91 | | |
89 | | - | |
| 92 | + | |
| 93 | + | |
90 | 94 | | |
91 | 95 | | |
92 | 96 | | |
| |||
109 | 113 | | |
110 | 114 | | |
111 | 115 | | |
112 | | - | |
| 116 | + | |
| 117 | + | |
113 | 118 | | |
114 | 119 | | |
115 | 120 | | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
116 | 129 | | |
117 | 130 | | |
118 | 131 | | |
| |||
137 | 150 | | |
138 | 151 | | |
139 | 152 | | |
140 | | - | |
| 153 | + | |
141 | 154 | | |
142 | 155 | | |
143 | 156 | | |
| |||
151 | 164 | | |
152 | 165 | | |
153 | 166 | | |
154 | | - | |
| 167 | + | |
| 168 | + | |
155 | 169 | | |
156 | 170 | | |
157 | 171 | | |
| |||
206 | 220 | | |
207 | 221 | | |
208 | 222 | | |
209 | | - | |
| 223 | + | |
210 | 224 | | |
211 | 225 | | |
212 | 226 | | |
| |||
220 | 234 | | |
221 | 235 | | |
222 | 236 | | |
223 | | - | |
| 237 | + | |
| 238 | + | |
224 | 239 | | |
225 | 240 | | |
226 | 241 | | |
| |||
280 | 295 | | |
281 | 296 | | |
282 | 297 | | |
283 | | - | |
| 298 | + | |
284 | 299 | | |
285 | 300 | | |
286 | 301 | | |
| |||
293 | 308 | | |
294 | 309 | | |
295 | 310 | | |
296 | | - | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
297 | 315 | | |
298 | 316 | | |
299 | 317 | | |
| |||
302 | 320 | | |
303 | 321 | | |
304 | 322 | | |
305 | | - | |
| 323 | + | |
| 324 | + | |
306 | 325 | | |
307 | 326 | | |
308 | 327 | | |
| |||
329 | 348 | | |
330 | 349 | | |
331 | 350 | | |
332 | | - | |
| 351 | + | |
0 commit comments