Skip to content

Commit d3be20e

Browse files
authored
fix(client-sdks): handle 204 empty responses with runtime validation (#288)
Adds e2e tests for endpoints returning `204` / empty responses and relaxes runtime schema validation for clients to use `any`, allowing for `axios` returning an `""` Keeps the type as `void` which is somewhat a lie, but I believe a pragmatic decision - in practice you shouldn't be looking at the response body for a `204` at all, so it shouldn't really matter. The `fetch` client will continue to throw a JSON parse error if you attempt to read the empty response, which I think is fine for the same reason. An alternative might've been to use a `.transform(it => undefined)` to coerce the runtime value to `undefined` but I don't think there is all that much value in this. closes #285
1 parent cead53a commit d3be20e

File tree

11 files changed

+115
-6
lines changed

11 files changed

+115
-6
lines changed

e2e/openapi.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ paths:
7474
application/json:
7575
schema:
7676
$ref: '#/components/schemas/RandomNumber'
77-
77+
/responses/empty:
78+
get:
79+
tags:
80+
- validation
81+
responses:
82+
204:
83+
description: Ok
7884
components:
7985
responses:
8086
GetHeaders:

e2e/src/generated/client/axios/client.ts

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/src/generated/client/fetch/client.ts

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/src/generated/routes/validation.ts

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/src/index.axios.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,13 @@ describe("e2e - typescript-axios client", () => {
198198
})
199199
})
200200
})
201+
202+
describe("GET /responses/empty", () => {
203+
it("returns undefined", async () => {
204+
const {status, data} = await client.getResponsesEmpty()
205+
206+
expect(status).toBe(204)
207+
expect(data).toEqual("")
208+
})
209+
})
201210
})

e2e/src/index.fetch.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,13 @@ describe("e2e - typescript-fetch client", () => {
224224
})
225225
})
226226
})
227+
228+
describe("GET /responses/empty", () => {
229+
it("returns undefined", async () => {
230+
const res = await client.getResponsesEmpty()
231+
232+
expect(res.status).toBe(204)
233+
await expect(res.json()).rejects.toThrow("Unexpected end of JSON input")
234+
})
235+
})
227236
})

e2e/src/routes/validation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
type GetResponsesEmpty,
23
type GetValidationNumbersRandomNumber,
34
createRouter,
45
} from "../generated/routes/validation"
@@ -28,8 +29,13 @@ const getValidationNumbersRandomNumber: GetValidationNumbersRandomNumber =
2829
return respond.withStatus(404)
2930
}
3031

32+
const getResponsesEmpty: GetResponsesEmpty = async (_, respond) => {
33+
return respond.with204()
34+
}
35+
3136
export function createValidationRouter() {
3237
return createRouter({
3338
getValidationNumbersRandomNumber,
39+
getResponsesEmpty,
3440
})
3541
}

packages/openapi-code-generator/src/typescript/common/client-operation-builder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class ClientOperationBuilder {
128128
acc.defaultResponse = {
129129
schema: content
130130
? schemaBuilder.fromModel(content.schema, true, true)
131-
: schemaBuilder.void(),
131+
: schemaBuilder.any(),
132132
type: content ? models.schemaObjectToType(content.schema) : "void",
133133
}
134134
} else {
@@ -138,7 +138,7 @@ export class ClientOperationBuilder {
138138
type: content ? models.schemaObjectToType(content.schema) : "void",
139139
schema: content
140140
? schemaBuilder.fromModel(content.schema, true, true)
141-
: schemaBuilder.void(),
141+
: schemaBuilder.any(),
142142
})
143143
}
144144

packages/openapi-code-generator/src/typescript/common/schema-builders/abstract-schema-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ export abstract class AbstractSchemaBuilder<
404404

405405
protected abstract boolean(): string
406406

407-
protected abstract any(): string
407+
public abstract any(): string
408408

409409
protected abstract unknown(): string
410410

packages/openapi-code-generator/src/typescript/common/schema-builders/joi-schema-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export class JoiBuilder extends AbstractSchemaBuilder<
259259
.join(".")
260260
}
261261

262-
protected any(): string {
262+
public any(): string {
263263
return [joi, "any()"].filter(isDefined).join(".")
264264
}
265265

0 commit comments

Comments
 (0)