Skip to content

Commit

Permalink
fix: openapi issues (#500)
Browse files Browse the repository at this point in the history
* fix: arbitrary status for empty response

* feat: allow a union schema for response headers
  • Loading branch information
sukovanej authored Mar 24, 2024
1 parent 542156e commit bf0f884
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 42 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-socks-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect-http": patch
---

Fix OpenApi with no body nor headers.
5 changes: 5 additions & 0 deletions .changeset/large-pumas-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect-http": patch
---

Allow a union schema for response headers.
2 changes: 1 addition & 1 deletion packages/effect-http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"coverage": "vitest --coverage"
},
"dependencies": {
"schema-openapi": "^0.33.7"
"schema-openapi": "^0.34.0"
},
"peerDependencies": {
"@effect/platform": "^0.48.15",
Expand Down
33 changes: 9 additions & 24 deletions packages/effect-http/src/internal/open-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,19 @@ export const make = (
const status = ApiResponse.getStatus(response)

if (ApiSchema.isIgnored(body) && ApiSchema.isIgnored(headers)) {
operationSpec.push(OpenApi.noContentResponse("No response"))
operationSpec.push(OpenApi.noContentResponse("No response", status))
continue
}

const schema = ApiSchema.isIgnored(body) ? undefined : body
const setHeaders = ApiSchema.isIgnored(headers)
? identity
: OpenApi.responseHeaders(
createResponseHeadersSchemaMap(headers)
)
const bodySchema = ApiSchema.isIgnored(body) ? undefined : body
const setHeaders = ApiSchema.isIgnored(headers) ? identity : createResponseHeaderSetter(headers)

operationSpec.push(
OpenApi.jsonResponse(
status as OpenApiTypes.OpenAPISpecStatusCode,
schema,
bodySchema,
`Response ${status}`,
schema ? descriptionSetter(schema) : identity,
bodySchema ? descriptionSetter(bodySchema) : identity,
setHeaders
)
)
Expand Down Expand Up @@ -245,21 +241,10 @@ const createParameterSetters = (
})
}

const createResponseHeadersSchemaMap = (schema: Schema.Schema<any, any, unknown>) => {
let ast = schema.ast

if (ast._tag === "Transformation") {
ast = ast.from
}
const createResponseHeaderSetter = (schema: Schema.Schema<any, any, unknown>) => {
const ps = getPropertySignatures("header", schema.ast)

if (ast._tag !== "TypeLiteral") {
throw new Error(`Response headers must be a type literal schema`)
}

return Object.fromEntries(
ast.propertySignatures.map((ps) => [
ps.name,
Schema.make<unknown, string, never>(ps.type)
])
return OpenApi.responseHeaders(
Object.fromEntries(ps.map((ps) => [ps.name, Schema.make(ps.type)]))
)
}
36 changes: 35 additions & 1 deletion packages/effect-http/test/openapi.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Schema } from "@effect/schema"
import { pipe } from "effect"
import { Api, ApiGroup, OpenApi, Security } from "effect-http"
import type { OpenAPISpecStatusCode } from "schema-openapi/OpenApiTypes"
import { expect, test } from "vitest"

test("description", () => {
Expand Down Expand Up @@ -208,7 +209,6 @@ test("union in query params", () => {
Api.make(),
Api.addEndpoint(
Api.post("myOperation", "/my-operation").pipe(
Api.setResponseBody(Schema.string),
Api.setRequestQuery(Schema.union(
Schema.struct({ a: Schema.string }),
Schema.struct({ a: Schema.number, b: Schema.string }),
Expand Down Expand Up @@ -293,3 +293,37 @@ test("http security scheme", () => {
}
})
})

test("arbitrary status with empty response is allowed", () => {
const api = pipe(
Api.make(),
Api.addEndpoint(
Api.post("myOperation", "/my-operation", { description: "options" }).pipe(
Api.addResponse({ status: 401 })
)
)
)

const openApi = OpenApi.make(api)

// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
expect(((openApi.paths["/my-operation"].post?.responses![401 as OpenAPISpecStatusCode])!).content).toBe(undefined)
})

test("response header as union", () => {
const api = pipe(
Api.make(),
Api.addEndpoint(
Api.post("myOperation", "/my-operation", { description: "options" }).pipe(
Api.setResponseHeaders(Schema.union(Schema.struct({}), Schema.struct({ a: Schema.string })))
)
)
)

const openApi = OpenApi.make(api)

// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
const headerSpec = ((openApi.paths["/my-operation"].post?.responses![200 as OpenAPISpecStatusCode])!).headers!["a"]

expect(headerSpec).toEqual({ description: "a string", schema: { description: "a string", type: "string" } })
})
32 changes: 16 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bf0f884

Please sign in to comment.