import { assert, describe, it } from "@effect/vitest" import { Schema } from "effect" import { Multipart } from "effect/unstable/http" import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" describe("OpenAPI spec", () => { describe("api", () => { it("annotate", () => { const Api = HttpApi.make("api") .annotate(OpenApi.Title, "title") .annotate(OpenApi.Version, "version") .annotate(OpenApi.Description, "description") .annotate(OpenApi.License, { name: "license" }) .annotate(OpenApi.Summary, "summary") .annotate(OpenApi.Servers, [{ url: "https://example.com" }]) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.info.title, "title") assert.deepStrictEqual(spec.info.version, "version") assert.deepStrictEqual(spec.info.description, "description") assert.deepStrictEqual(spec.info.license, { name: "license" }) assert.deepStrictEqual(spec.info.summary, "summary") assert.deepStrictEqual(spec.servers, [{ url: "https://example.com" }]) }) it("annotateMerge", () => { const Api = HttpApi.make("api").annotateMerge(OpenApi.annotations({ title: "title", version: "version", description: "description", license: { name: "license" }, summary: "summary", servers: [{ url: "https://example.com" }] })) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.info.title, "title") assert.deepStrictEqual(spec.info.version, "version") assert.deepStrictEqual(spec.info.description, "description") assert.deepStrictEqual(spec.info.license, { name: "license" }) assert.deepStrictEqual(spec.info.summary, "summary") assert.deepStrictEqual(spec.servers, [{ url: "https://example.com" }]) }) it("AdditionalSchemas", () => { const Api = HttpApi.make("api") .annotate(HttpApi.AdditionalSchemas, [Schema.String.annotate({ identifier: "A" })]) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.components.schemas, { A: { "type": "string" } }) }) it("Override", () => { const Api = HttpApi.make("api") .annotate(OpenApi.Override, { tags: [{ name: "A" }] }) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.tags, [{ name: "A" }]) }) it("Transform", () => { const Api = HttpApi.make("api") .annotate(OpenApi.Transform, (spec) => ({ ...spec, tags: [{ name: "A" }] })) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.tags, [{ name: "A" }]) }) }) describe("group", () => { it("annotations", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") // Add a description for the group .annotate(OpenApi.Description, "my description") // Provide external documentation links .annotate(OpenApi.ExternalDocs, { url: "http://example.com", description: "example" }) // Override parts of the final output .annotate(OpenApi.Override, { name: "my name" }) // Transform the final specification for this group .annotate(OpenApi.Transform, (spec) => ({ ...spec, name: spec.name + "-transformed" })) ) .add( HttpApiGroup.make("excluded") // Exclude the group from the final specification .annotate(OpenApi.Exclude, true) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec, { "openapi": "3.1.0", "info": { "title": "Api", "version": "0.0.1" }, "paths": {}, "components": { "schemas": {}, "securitySchemes": {} }, "security": [], "tags": [ { "name": "my name-transformed", "description": "my description", "externalDocs": { "url": "http://example.com", "description": "example" } } ] }) }) it("top level group", () => { const Api = HttpApi.make("api").add( // Mark the group as top-level HttpApiGroup.make("group", { topLevel: true }).add( HttpApiEndpoint.get("get", "/", { success: Schema.String }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths, { "/": { "get": { "tags": [ "group" ], "operationId": "get", "parameters": [], "security": [], "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "type": "string" } } } } } } } }) }) }) describe("endpoint", () => { describe("annotations", () => { it("endpoint annotations", () => { const Api = HttpApi.make("api").add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("get", "/", { success: Schema.String }) // Add a description .annotate(OpenApi.Description, "my description") // Provide a summary .annotate(OpenApi.Summary, "my summary") // Mark the endpoint as deprecated .annotate(OpenApi.Deprecated, true) // Provide external documentation .annotate(OpenApi.ExternalDocs, { url: "http://example.com", description: "example" }) ) .add( HttpApiEndpoint.get("excluded", "/excluded", { success: Schema.String }) // Exclude this endpoint from the final specification .annotate(OpenApi.Exclude, true) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec, { "openapi": "3.1.0", "info": { "title": "Api", "version": "0.0.1" }, "paths": { "/": { "get": { "tags": [ "group" ], "operationId": "group.get", "parameters": [], "security": [], "responses": { "200": { "description": "Success", "content": { "application/json": { "schema": { "type": "string" } } } } }, "description": "my description", "summary": "my summary", "deprecated": true, "externalDocs": { "url": "http://example.com", "description": "example" } } } }, "components": { "schemas": {}, "securitySchemes": {} }, "security": [], "tags": [ { "name": "group" } ] }) }) it("success description", () => { const User = Schema.Struct({ id: Schema.Finite, name: Schema.String }).annotate({ identifier: "User" }) const Api = HttpApi.make("api").add( HttpApiGroup.make("group").add( HttpApiEndpoint.get("getUsers", "/users", { success: Schema.Array(User).annotate({ description: "Returns an array of users" }) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths, { "/users": { "get": { "tags": [ "group" ], "operationId": "group.getUsers", "parameters": [], "security": [], "responses": { "200": { "description": "Returns an array of users", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/User" }, "description": "Returns an array of users" } } } } } } } }) assert.deepStrictEqual(spec.components.schemas, { User: { "type": "object", "properties": { "id": { "type": "number" }, "name": { "type": "string" } }, "required": [ "id", "name" ], "additionalProperties": false } }) }) }) it("catch all path", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "*", { success: Schema.String }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["*"].get?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } } }) }) it("middleware error", () => { class M extends HttpApiMiddleware.Service()("Http/Logger", { error: Schema.String .pipe( HttpApiSchema.status(405), HttpApiSchema.asText() ) }) {} const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: Schema.Finite }) ).middleware(M) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "number" } } } }, "405": { description: "Error", content: { "text/plain": { schema: { "type": "string" } } } } }) }) describe("params option", () => { it("GET", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a/:id", { params: { id: Schema.FiniteFromString } }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a/{id}"].get?.parameters, [ { name: "id", in: "path", schema: { "type": "string" }, required: true } ]) }) }) describe("payload option", () => { describe("GET", () => { it("query parameters", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { payload: { required: Schema.FiniteFromString, optionalKey: Schema.optionalKey(Schema.FiniteFromString), optional: Schema.optional(Schema.FiniteFromString) } }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.parameters, [ { name: "required", in: "query", schema: { "type": "string" }, required: true }, { name: "optionalKey", in: "query", schema: { "type": "string" }, required: false }, { name: "optional", in: "query", schema: { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, required: false } ]) }) }) describe("POST", () => { it("NoContent", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.post("a", "/a", { payload: HttpApiSchema.NoContent })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody, undefined) }) it("NoContent + payload", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.post("a", "/a", { payload: [HttpApiSchema.NoContent, Schema.String] })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "application/json": { schema: { "type": "string" } } }) }) it("Json (default)", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.String }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "application/json": { schema: { "type": "string" } } }) }) describe("encodings", () => { it("asMultipart", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.String.pipe(HttpApiSchema.asMultipart()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "multipart/form-data": { schema: { "type": "string" } } }) }) it("asMultipartStream", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.String.pipe(HttpApiSchema.asMultipartStream()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "multipart/form-data": { schema: { "type": "string" } } }) }) it("asJson + contentType", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.String.pipe( HttpApiSchema.asJson({ contentType: "application/problem+json" }) ) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "application/problem+json": { schema: { "type": "string" } } }) }) it("asText", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.String.pipe(HttpApiSchema.asText()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "text/plain": { schema: { "type": "string" } } }) }) it("asFormUrlEncoded", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.Struct({ a: Schema.String }).pipe( HttpApiSchema.asFormUrlEncoded() ) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "application/x-www-form-urlencoded": { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } }) }) it("asUint8Array", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "application/octet-stream": { schema: { "type": "string", "format": "binary" } } }) }) it("array of schemas with different encodings", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: [ Schema.Struct({ a: Schema.String }), // application/json Schema.String.pipe(HttpApiSchema.asText()), // text/plain Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()) // application/octet-stream ] }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "application/json": { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } }, "text/plain": { schema: { "type": "string" } }, "application/octet-stream": { schema: { "type": "string", "format": "binary" } } }) }) it("asMultipart with FilesSchema", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Multipart.FilesSchema.pipe(HttpApiSchema.asMultipart()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "multipart/form-data": { schema: { type: "array", items: { type: "string", format: "binary" } } } }) }) it("asMultipart with SingleFileSchema", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Multipart.SingleFileSchema.pipe(HttpApiSchema.asMultipart()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "multipart/form-data": { schema: { type: "string", format: "binary" } } }) }) it("asMultipart with SingleFileSchema in Struct", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.Struct({ file: Multipart.SingleFileSchema }).pipe(HttpApiSchema.asMultipart()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "multipart/form-data": { schema: { type: "object", properties: { file: { type: "string", format: "binary" } }, required: ["file"], additionalProperties: false } } }) }) it("FilesSchema in non-multipart payload should not be converted to binary", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.post("a", "/a", { payload: Schema.Struct({ files: Multipart.FilesSchema }) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.requestBody?.content, { "application/json": { schema: { type: "object", properties: { files: { type: "array", items: { type: "object", properties: { key: { "type": "string" }, name: { "type": "string" }, contentType: { type: "string", contentEncoding: "binary" }, path: { "type": "string" } }, required: ["key", "name", "contentType", "path"], additionalProperties: false } } }, required: ["files"], additionalProperties: false } } }) }) }) }) }) describe("success option", () => { it("no content (default)", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.get("a", "/a")) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["204"], { description: "" }) }) it("schema", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.get("a", "/a", { success: Schema.String })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "application/json": { schema: { "type": "string" } } }) }) describe("NoContent", () => { it("makeNoContent(204)", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.Empty(204) })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["204"], { description: "" }) }) it("NoContent", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.NoContent })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["204"], { description: "" }) }) it("Created", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.Created })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["201"], { description: "" }) }) it("status(\"Created\")", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.get("a", "/a", { success: Schema.Void.pipe(HttpApiSchema.status("Created")) })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["201"], { description: "" }) }) it("Accepted", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add(HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.Accepted })) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["202"], { description: "" }) }) }) describe("encodings", () => { it("asJson + contentType", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: Schema.String.pipe( HttpApiSchema.asJson({ contentType: "application/problem+json" }) ) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "application/problem+json": { schema: { "type": "string" } } }) }) it("asText", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: Schema.String.pipe(HttpApiSchema.asText()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "text/plain": { schema: { "type": "string" } } }) }) it("asFormUrlEncoded", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: Schema.Struct({ a: Schema.String }).pipe( HttpApiSchema.asFormUrlEncoded() ) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "application/x-www-form-urlencoded": { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } }) }) it("asUint8Array", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "application/octet-stream": { schema: { "type": "string", "format": "binary" } } }) }) it("StreamSse", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamSse({ events: Schema.Struct({ event: Schema.Literal("message"), data: Schema.String }), error: Schema.Struct({ message: Schema.String }) }) }) ) ) const spec = OpenApi.fromApi(Api) const mediaType = spec.paths["/a"].get?.responses["200"].content?.["text/event-stream"] const stream = mediaType?.["x-effect-stream"] assert.strictEqual(stream?.encoding, "sse") if (stream?.encoding !== "sse") { assert.fail("expected SSE stream metadata") } assert.strictEqual(stream.failureEvent, "effect/httpapi/stream/failure") assert.strictEqual(typeof stream.causeSchema, "object") assert.strictEqual(typeof stream.errorSchema, "object") }) it("StreamSse with annotated status", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.status(202)( HttpApiSchema.StreamSse({ events: Schema.Struct({ event: Schema.Literal("message"), data: Schema.String }), error: Schema.Struct({ message: Schema.String }) }) ) }) ) ) const spec = OpenApi.fromApi(Api) assert.strictEqual(spec.paths["/a"].get?.responses["200"], undefined) assert.strictEqual( spec.paths["/a"].get?.responses["202"].content?.["text/event-stream"]?.["x-effect-stream"]?.encoding, "sse" ) }) it("StreamUint8Array", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamUint8Array() }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "application/octet-stream": { schema: { "type": "string", "format": "binary" }, "x-effect-stream": { encoding: "uint8array" } } }) }) it("StreamUint8Array with annotated status", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.status(206)(HttpApiSchema.StreamUint8Array()) }) ) ) const spec = OpenApi.fromApi(Api) assert.strictEqual(spec.paths["/a"].get?.responses["200"], undefined) assert.deepStrictEqual(spec.paths["/a"].get?.responses["206"].content, { "application/octet-stream": { schema: { "type": "string", "format": "binary" }, "x-effect-stream": { encoding: "uint8array" } } }) }) it("union with top level encoding", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: Schema.Union([ Schema.Struct({ a: Schema.String }), Schema.Struct({ b: Schema.String }) ]).pipe(HttpApiSchema.asFormUrlEncoded()) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "application/x-www-form-urlencoded": { schema: { "anyOf": [ { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false }, { "type": "object", "properties": { "b": { "type": "string" } }, "required": ["b"], "additionalProperties": false } ] } } }) }) it("asJson + astText", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { success: [ Schema.String, Schema.String.pipe( HttpApiSchema.asText() ) ] }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["200"].content, { "application/json": { "schema": { "type": "string" } }, "text/plain": { "schema": { "type": "string" } } }) }) }) }) describe("error option", () => { it("should deduplicate the descriptions", () => { class Group extends HttpApiGroup.make("users") .add(HttpApiEndpoint.post("a", "/a", { error: HttpApiError.BadRequestNoContent })) {} class Api extends HttpApi.make("api").add(Group) {} const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.responses["400"], { description: "BadRequest" }) assert.deepStrictEqual(spec.components.schemas, {}) }) describe("1 endopoint", () => { it("no identifier annotation", () => { class Group extends HttpApiGroup.make("users") .add(HttpApiEndpoint.post("a", "/a", { payload: Schema.String, success: Schema.String, error: Schema.String })) {} class Api extends HttpApi.make("api").add(Group) {} const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "500": { description: "Error", content: { "application/json": { schema: { "type": "string" } } } } }) assert.deepStrictEqual(spec.components.schemas, {}) }) it("identifier annotation", () => { class Group extends HttpApiGroup.make("users") .add(HttpApiEndpoint.post("a", "/a", { payload: Schema.String, success: Schema.String, error: Schema.String.annotate({ identifier: "id" }) })) {} class Api extends HttpApi.make("api").add(Group) {} const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "500": { description: "id", content: { "application/json": { schema: { "$ref": "#/components/schemas/id" } } } } }) assert.deepStrictEqual(spec.components.schemas, { id: { "type": "string" } }) }) it("httpApiStatus annotation", () => { class Group extends HttpApiGroup.make("users") .add(HttpApiEndpoint.post("a", "/a", { payload: Schema.String, success: Schema.String, error: Schema.String.annotate({ httpApiStatus: 400 }) })) {} class Api extends HttpApi.make("api").add(Group) {} const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "400": { description: "Error", content: { "application/json": { schema: { "type": "string" } } } } }) assert.deepStrictEqual(spec.components.schemas, {}) }) }) describe("2 endopoints", () => { it("no identifier annotation", () => { const E = Schema.String class Group extends HttpApiGroup.make("users") .add(HttpApiEndpoint.post("a", "/a", { payload: Schema.String, success: Schema.String, error: E })) .add(HttpApiEndpoint.post("b", "/b", { payload: Schema.String, success: Schema.String, error: E })) {} class Api extends HttpApi.make("api").add(Group) {} const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "500": { description: "Error", content: { "application/json": { schema: { "type": "string" } } } } }) assert.deepStrictEqual(spec.paths["/b"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "500": { description: "Error", content: { "application/json": { schema: { "type": "string" } } } } }) assert.deepStrictEqual(spec.components.schemas, {}) }) it("identifier annotation", () => { const E = Schema.String.annotate({ identifier: "id" }) class Group extends HttpApiGroup.make("users") .add(HttpApiEndpoint.post("a", "/a", { payload: Schema.String, success: Schema.String, error: E })) .add(HttpApiEndpoint.post("b", "/b", { payload: Schema.String, success: Schema.String, error: E })) {} class Api extends HttpApi.make("api").add(Group) {} const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "500": { description: "id", content: { "application/json": { schema: { "$ref": "#/components/schemas/id" } } } } }) assert.deepStrictEqual(spec.paths["/b"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "500": { description: "id", content: { "application/json": { schema: { "$ref": "#/components/schemas/id" } } } } }) assert.deepStrictEqual(spec.components.schemas, { id: { "type": "string" } }) }) it("httpApiStatus annotation", () => { const E = Schema.String.annotate({ httpApiStatus: 400 }) class Group extends HttpApiGroup.make("users") .add(HttpApiEndpoint.post("a", "/a", { payload: Schema.String, success: Schema.String, error: E })) .add(HttpApiEndpoint.post("b", "/b", { payload: Schema.String, success: Schema.String, error: E })) {} class Api extends HttpApi.make("api").add(Group) {} const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "400": { description: "Error", content: { "application/json": { schema: { "type": "string" } } } } }) assert.deepStrictEqual(spec.paths["/b"].post?.responses, { "200": { description: "Success", content: { "application/json": { schema: { "type": "string" } } } }, "400": { description: "Error", content: { "application/json": { schema: { "type": "string" } } } } }) assert.deepStrictEqual(spec.components.schemas, {}) }) }) describe("No Content", () => { it("makeNoContent(400)", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { error: HttpApiSchema.Empty(400) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["400"], { description: "" }) }) it("Empty(401)", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { error: HttpApiSchema.Empty(401) }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["401"], { description: "" }) }) it("UnauthorizedNoContent", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { error: HttpApiError.UnauthorizedNoContent }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["401"], { description: "Unauthorized" }) }) it("BadRequestNoContent", () => { const Api = HttpApi.make("api") .add( HttpApiGroup.make("group") .add( HttpApiEndpoint.get("a", "/a", { error: HttpApiError.BadRequestNoContent }) ) ) const spec = OpenApi.fromApi(Api) assert.deepStrictEqual(spec.paths["/a"].get?.responses["400"], { description: "BadRequest" }) }) }) }) }) })