diff --git a/openapi-example.yaml b/openapi-example.yaml index c5f9de0d..e0ce861f 100644 --- a/openapi-example.yaml +++ b/openapi-example.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - version: 1.0.0 + version: 1.0.0 title: Sample API description: A sample API to illustrate OpenAPI concepts paths: @@ -12,9 +12,8 @@ paths: description: Successful response content: application/json: - schema: - type: - string + schema: + type: string examples: no visits: value: You have not visited anyone yet @@ -26,9 +25,8 @@ paths: description: Successful response content: application/json: - schema: - type: - string + schema: + type: string examples: hello kitty: value: >- @@ -53,12 +51,39 @@ paths: type: string content: application/json: - schema: - type: - string + schema: + type: string example: an example string examples: hello-example1: value: Hello, example1 hello-example2: value: Hello, example2 + /path-one: + get: + responses: + "400": + $ref: "#/components/responses/BadRequest" + +components: + responses: + BadRequest: + description: The request is malformed and so cannot be processed + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + schemas: + Error: + title: Error + description: A generic error message suitable for 4xx and 5xx responses + type: object + properties: + code: + type: string + description: A machine readable error code + message: + type: string + description: A detailed description of the error + required: + - message diff --git a/src/typescript-generator/operation-type-coder.js b/src/typescript-generator/operation-type-coder.js index ff0eb3a9..09ec9429 100644 --- a/src/typescript-generator/operation-type-coder.js +++ b/src/typescript-generator/operation-type-coder.js @@ -1,5 +1,7 @@ import nodePath from "node:path"; +import createDebug from "debug"; + import { Coder } from "./coder.js"; import { CONTEXT_FILE_TOKEN } from "./context-file-token.js"; import { ParametersTypeCoder } from "./parameters-type-coder.js"; @@ -7,6 +9,10 @@ import { READ_ONLY_COMMENTS } from "./read-only-comments.js"; import { ResponseTypeCoder } from "./response-type-coder.js"; import { SchemaTypeCoder } from "./schema-type-coder.js"; +const debug = createDebug( + "counterfact:typescript-generator:coder:operation-type", +); + export class OperationTypeCoder extends Coder { names() { return super.names( @@ -14,15 +20,33 @@ export class OperationTypeCoder extends Coder { ); } + hackyDereference(originalResponse) { + const reference = originalResponse.data.$ref; + + if (!reference.startsWith("#/")) { + throw new Error( + `Cannot look up "${reference}"; this is a bug in Counterfact.`, + ); + } + + return this.requirement.specification.rootRequirement.select( + reference.slice(2), + ); + } + responseTypes(script) { return this.requirement .get("responses") - .flatMap((response, responseCode) => { + .flatMap((originalResponse, responseCode) => { const status = responseCode === "default" ? "number | undefined" : Number.parseInt(responseCode, 10); + const response = originalResponse.isReference + ? this.hackyDereference(originalResponse) + : originalResponse; + if (response.has("content")) { return response.get("content").map( (content, contentType) => `{ @@ -70,6 +94,8 @@ export class OperationTypeCoder extends Coder { // eslint-disable-next-line max-statements writeCode(script) { + debug("writing code for %s", script.path); + // eslint-disable-next-line no-param-reassign script.comments = READ_ONLY_COMMENTS; diff --git a/src/typescript-generator/response-type-coder.js b/src/typescript-generator/response-type-coder.js index 1e51aa2f..9f081ad9 100644 --- a/src/typescript-generator/response-type-coder.js +++ b/src/typescript-generator/response-type-coder.js @@ -1,7 +1,13 @@ +import createDebug from "debug"; + import { Coder } from "./coder.js"; import { printObject, printObjectWithoutQuotes } from "./printers.js"; import { SchemaTypeCoder } from "./schema-type-coder.js"; +const debug = createDebug( + "countefact:typescript-generator:coder:response-type", +); + export class ResponseTypeCoder extends Coder { constructor(requirement, openApi2MediaTypes = []) { super(requirement); @@ -99,6 +105,8 @@ export class ResponseTypeCoder extends Coder { } writeCode(script) { + debug("writing code for %s", script.path); + script.importSharedType("ResponseBuilderFactory"); const text = `ResponseBuilderFactory<${this.buildResponseObjectType( diff --git a/test/__snapshots__/black-box.test.js.snap b/test/__snapshots__/black-box.test.js.snap index e7667da3..5a04508c 100644 --- a/test/__snapshots__/black-box.test.js.snap +++ b/test/__snapshots__/black-box.test.js.snap @@ -16,4 +16,48 @@ export const GET: HTTP_GET = ($) => { " `; +exports[`black box test creates a type for the /hello/kitty path 1`] = ` +"// This code was automatically generated from an OpenAPI description. +// Do not edit this file. Edit the OpenAPI file instead. +// For more information, see https://github.com/pmcelhaney/counterfact/blob/main/docs/faq-generated-code.md + +import type { WideOperationArgument } from "../../types.d.ts"; +import type { OmitValueWhenNever } from "../../types.d.ts"; +import type { Context } from "../../paths/_.context.ts"; +import type { ResponseBuilderFactory } from "../../types.d.ts"; +import type { HttpStatusCode } from "../../types.d.ts"; + +export type HTTP_GET = ( + $: OmitValueWhenNever<{ + query: never; + path: never; + header: never; + body: never; + context: Context; + response: ResponseBuilderFactory<{ + [statusCode in HttpStatusCode]: { + headers: never; + requiredHeaders: never; + content: { + "application/json": { + schema: string; + }; + }; + }; + }>; + x: WideOperationArgument; + proxy: (url: string) => "COUNTERFACT_RESPONSE"; + }>, +) => + | { + status: number | undefined; + contentType?: "application/json"; + body?: string; + } + | { status: 415; contentType: "text/plain"; body: string } + | "COUNTERFACT_RESPONSE" + | { ALL_REMAINING_HEADERS_ARE_OPTIONAL: "COUNTERFACT_RESPONSE" }; +" +`; + exports[`black box test responds to a GET request 1`] = `""`; diff --git a/test/black-box.test.js b/test/black-box.test.js index fa903e96..61456f1a 100644 --- a/test/black-box.test.js +++ b/test/black-box.test.js @@ -63,6 +63,12 @@ describe("black box test", () => { ).toMatchSnapshot(); }); + it("creates a type for the /hello/kitty path", () => { + expect( + fs.readFileSync("./out/path-types/hello/kitty.types.ts", "utf8"), + ).toMatchSnapshot(); + }); + it("compiles kitty.ts", () => { expect( fs.readFileSync("./out/.cache/hello/kitty.js", "utf8"),