Skip to content

Commit a5c5159

Browse files
Smaug123claude
andcommitted
Represent "default" responses instead of crashing on them
Response keys were unconditionally Int32.Parsed, so any valid Swagger 2 spec containing a "default" response aborted generation with a FormatException. Responses are now keyed by a ResponseKey DU (Code of int | Default), and the client generator explicitly ignores Default when selecting the success response: it describes the status codes not otherwise listed, which in practice means errors. No generated output changes: Gitea declares no default responses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d7440bb commit a5c5159

3 files changed

Lines changed: 70 additions & 13 deletions

File tree

WoofWare.Myriad.Plugins.Test/TestSwagger/TestSwaggerParse.fs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ module TestSwaggerParse =
7676
|> Some
7777
Responses =
7878
[
79-
204, Definition.Unspecified
80-
303, Definition.Unspecified
81-
404, Definition.Unspecified
79+
ResponseKey.Code 204, Definition.Unspecified
80+
ResponseKey.Code 303, Definition.Unspecified
81+
ResponseKey.Code 404, Definition.Unspecified
8282
]
8383
|> Map.ofList
8484
}
@@ -118,12 +118,50 @@ module TestSwaggerParse =
118118
endpoint.Responses
119119
|> shouldEqual (
120120
[
121-
200,
121+
ResponseKey.Code 200,
122122
Definition.Array
123123
{
124124
Items = Definition.String
125125
}
126-
403, Definition.Handle "#/responses/forbidden"
126+
ResponseKey.Code 403, Definition.Handle "#/responses/forbidden"
127+
]
128+
|> Map.ofList
129+
)
130+
131+
[<Test>]
132+
let ``Can parse a default response`` () : unit =
133+
let s =
134+
"""{
135+
"tags": [
136+
"pet"
137+
],
138+
"summary": "Returns all pets from the system that the user has access to",
139+
"operationId": "findPets",
140+
"responses": {
141+
"200": {
142+
"description": "pet response",
143+
"schema": {
144+
"type": "string"
145+
}
146+
},
147+
"default": {
148+
"description": "unexpected error",
149+
"schema": {
150+
"$ref": "#/definitions/Error"
151+
}
152+
}
153+
}
154+
}
155+
"""
156+
|> JsonNode.Parse
157+
158+
let endpoint = s.AsObject () |> SwaggerEndpoint.Parse
159+
160+
endpoint.Responses
161+
|> shouldEqual (
162+
[
163+
ResponseKey.Code 200, Definition.String
164+
ResponseKey.Default, Definition.Handle "#/definitions/Error"
127165
]
128166
|> Map.ofList
129167
)

WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -655,10 +655,13 @@ module internal SwaggerV2Generator =
655655
let returnType =
656656
endpoint.Responses
657657
|> Seq.choose (fun (KeyValue (response, defn)) ->
658-
if 200 <= response && response < 300 then
659-
Some defn
660-
else
661-
None
658+
match response with
659+
| SwaggerV2.ResponseKey.Code code when 200 <= code && code < 300 -> Some defn
660+
| SwaggerV2.ResponseKey.Code _
661+
// The "default" response describes what comes back for status
662+
// codes not otherwise listed, which in practice means errors;
663+
// it doesn't contribute to the success return type.
664+
| SwaggerV2.ResponseKey.Default -> None
662665
)
663666
|> Seq.toList
664667

WoofWare.Myriad.Plugins/SwaggerV2.fs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,22 @@ type Response =
330330
Schema = schema
331331
}
332332

333+
/// Key into an endpoint's "responses" map: either a specific HTTP status code,
334+
/// or the catch-all "default", which describes all responses whose status codes
335+
/// are not otherwise listed.
336+
type ResponseKey =
337+
/// A specific HTTP status code, e.g. 200.
338+
| Code of int
339+
/// The catch-all "default" response.
340+
| Default
341+
342+
/// Parse a key of the "responses" object, e.g. "200" or "default".
343+
static member Parse (s : string) : ResponseKey =
344+
if s = "default" then
345+
ResponseKey.Default
346+
else
347+
ResponseKey.Code (Int32.Parse s)
348+
333349
/// An "endpoint" is basically a single HTTP verb, applied to some path.
334350
type SwaggerEndpoint =
335351
{
@@ -350,9 +366,9 @@ type SwaggerEndpoint =
350366
/// (Each parameter knows how it needs to be supplied: e.g. if it's a query parameter or
351367
/// if it's interpolated into the path.)
352368
Parameters : SwaggerParameter list option
353-
/// Map of HTTP response code to the type that we expect to receive in the body if we
354-
/// get that response code back.
355-
Responses : Map<int, Definition>
369+
/// Map of HTTP response code (or the catch-all "default") to the type that we
370+
/// expect to receive in the body if we get that response code back.
371+
Responses : Map<ResponseKey, Definition>
356372
}
357373

358374
/// Render a JsonObject into this strongly-typed specification.
@@ -375,7 +391,7 @@ type SwaggerEndpoint =
375391
| Some _ -> Definition.Parse value
376392
| None -> (Response.Parse value).Schema
377393

378-
Int32.Parse key, defn
394+
ResponseKey.Parse key, defn
379395
)
380396
|> Map.ofSeq
381397

0 commit comments

Comments
 (0)