Skip to content

Commit bf0f884

Browse files
authored
fix: openapi issues (#500)
* fix: arbitrary status for empty response * feat: allow a union schema for response headers
1 parent 542156e commit bf0f884

File tree

6 files changed

+71
-42
lines changed

6 files changed

+71
-42
lines changed

.changeset/fair-socks-jog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect-http": patch
3+
---
4+
5+
Fix OpenApi with no body nor headers.

.changeset/large-pumas-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect-http": patch
3+
---
4+
5+
Allow a union schema for response headers.

packages/effect-http/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"coverage": "vitest --coverage"
3131
},
3232
"dependencies": {
33-
"schema-openapi": "^0.33.7"
33+
"schema-openapi": "^0.34.0"
3434
},
3535
"peerDependencies": {
3636
"@effect/platform": "^0.48.15",

packages/effect-http/src/internal/open-api.ts

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,19 @@ export const make = (
3131
const status = ApiResponse.getStatus(response)
3232

3333
if (ApiSchema.isIgnored(body) && ApiSchema.isIgnored(headers)) {
34-
operationSpec.push(OpenApi.noContentResponse("No response"))
34+
operationSpec.push(OpenApi.noContentResponse("No response", status))
3535
continue
3636
}
3737

38-
const schema = ApiSchema.isIgnored(body) ? undefined : body
39-
const setHeaders = ApiSchema.isIgnored(headers)
40-
? identity
41-
: OpenApi.responseHeaders(
42-
createResponseHeadersSchemaMap(headers)
43-
)
38+
const bodySchema = ApiSchema.isIgnored(body) ? undefined : body
39+
const setHeaders = ApiSchema.isIgnored(headers) ? identity : createResponseHeaderSetter(headers)
4440

4541
operationSpec.push(
4642
OpenApi.jsonResponse(
4743
status as OpenApiTypes.OpenAPISpecStatusCode,
48-
schema,
44+
bodySchema,
4945
`Response ${status}`,
50-
schema ? descriptionSetter(schema) : identity,
46+
bodySchema ? descriptionSetter(bodySchema) : identity,
5147
setHeaders
5248
)
5349
)
@@ -245,21 +241,10 @@ const createParameterSetters = (
245241
})
246242
}
247243

248-
const createResponseHeadersSchemaMap = (schema: Schema.Schema<any, any, unknown>) => {
249-
let ast = schema.ast
250-
251-
if (ast._tag === "Transformation") {
252-
ast = ast.from
253-
}
244+
const createResponseHeaderSetter = (schema: Schema.Schema<any, any, unknown>) => {
245+
const ps = getPropertySignatures("header", schema.ast)
254246

255-
if (ast._tag !== "TypeLiteral") {
256-
throw new Error(`Response headers must be a type literal schema`)
257-
}
258-
259-
return Object.fromEntries(
260-
ast.propertySignatures.map((ps) => [
261-
ps.name,
262-
Schema.make<unknown, string, never>(ps.type)
263-
])
247+
return OpenApi.responseHeaders(
248+
Object.fromEntries(ps.map((ps) => [ps.name, Schema.make(ps.type)]))
264249
)
265250
}

packages/effect-http/test/openapi.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Schema } from "@effect/schema"
22
import { pipe } from "effect"
33
import { Api, ApiGroup, OpenApi, Security } from "effect-http"
4+
import type { OpenAPISpecStatusCode } from "schema-openapi/OpenApiTypes"
45
import { expect, test } from "vitest"
56

67
test("description", () => {
@@ -208,7 +209,6 @@ test("union in query params", () => {
208209
Api.make(),
209210
Api.addEndpoint(
210211
Api.post("myOperation", "/my-operation").pipe(
211-
Api.setResponseBody(Schema.string),
212212
Api.setRequestQuery(Schema.union(
213213
Schema.struct({ a: Schema.string }),
214214
Schema.struct({ a: Schema.number, b: Schema.string }),
@@ -293,3 +293,37 @@ test("http security scheme", () => {
293293
}
294294
})
295295
})
296+
297+
test("arbitrary status with empty response is allowed", () => {
298+
const api = pipe(
299+
Api.make(),
300+
Api.addEndpoint(
301+
Api.post("myOperation", "/my-operation", { description: "options" }).pipe(
302+
Api.addResponse({ status: 401 })
303+
)
304+
)
305+
)
306+
307+
const openApi = OpenApi.make(api)
308+
309+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
310+
expect(((openApi.paths["/my-operation"].post?.responses![401 as OpenAPISpecStatusCode])!).content).toBe(undefined)
311+
})
312+
313+
test("response header as union", () => {
314+
const api = pipe(
315+
Api.make(),
316+
Api.addEndpoint(
317+
Api.post("myOperation", "/my-operation", { description: "options" }).pipe(
318+
Api.setResponseHeaders(Schema.union(Schema.struct({}), Schema.struct({ a: Schema.string })))
319+
)
320+
)
321+
)
322+
323+
const openApi = OpenApi.make(api)
324+
325+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
326+
const headerSpec = ((openApi.paths["/my-operation"].post?.responses![200 as OpenAPISpecStatusCode])!).headers!["a"]
327+
328+
expect(headerSpec).toEqual({ description: "a string", schema: { description: "a string", type: "string" } })
329+
})

pnpm-lock.yaml

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

0 commit comments

Comments
 (0)