Skip to content

Commit

Permalink
not very elegant fix for $ref in responses (fixes #838)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmcelhaney committed Apr 10, 2024
1 parent 91cb87e commit 9ad8a04
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 11 deletions.
45 changes: 35 additions & 10 deletions 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:
Expand All @@ -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
Expand All @@ -26,9 +25,8 @@ paths:
description: Successful response
content:
application/json:
schema:
type:
string
schema:
type: string
examples:
hello kitty:
value: >-
Expand All @@ -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
28 changes: 27 additions & 1 deletion src/typescript-generator/operation-type-coder.js
@@ -1,28 +1,52 @@
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";
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(
`HTTP_${this.requirement.url.split("/").at(-1).toUpperCase()}`,
);
}

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) => `{
Expand Down Expand Up @@ -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;

Expand Down
8 changes: 8 additions & 0 deletions 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);
Expand Down Expand Up @@ -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(
Expand Down
44 changes: 44 additions & 0 deletions test/__snapshots__/black-box.test.js.snap
Expand Up @@ -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`] = `"<img src="https://upload.wikimedia.org/wikipedia/en/0/05/Hello_kitty_character_portrait.png">"`;
6 changes: 6 additions & 0 deletions test/black-box.test.js
Expand Up @@ -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"),
Expand Down

0 comments on commit 9ad8a04

Please sign in to comment.