Skip to content

Commit

Permalink
Merge pull request #852 from pmcelhaney/refactor-path-coder
Browse files Browse the repository at this point in the history
Support OpenAPI reusable responses in global components object (handle $ref properly everywhere)
  • Loading branch information
pmcelhaney committed Apr 16, 2024
2 parents 51ba255 + 0f9a78c commit 9762407
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 121 deletions.
52 changes: 42 additions & 10 deletions openapi-example.yaml
Original file line number Diff line number Diff line change
@@ -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,46 @@ 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:
"200":
"description": "test"
content:
application/json:
schema:
type: string
"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
14 changes: 11 additions & 3 deletions src/typescript-generator/coder.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ export class Coder {
return "";
}

write() {
// This method should be overridden by a subclass.
write(script) {
if (this.requirement.isReference) {
return script.import(this);
}

return this.writeCode(script);
}

return `/* ${this.id} */`;
writeCode() {
throw new Error(
"write() is abstract and should be overwritten by a subclass",
);
}

async delegate() {
Expand Down
2 changes: 0 additions & 2 deletions src/typescript-generator/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ export async function generate(
});
});

// repository.get("paths/$.context.ts").exportDefault(new ContextCoder(paths));

debug("telling the repository to write the files to %s", destination);

await repository.writeFiles(destination);
Expand Down
10 changes: 5 additions & 5 deletions src/typescript-generator/operation-type-coder.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import nodePath from "node:path";

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 { ResponsesTypeCoder } from "./responses-type-coder.js";
import { SchemaTypeCoder } from "./schema-type-coder.js";
import { TypeCoder } from "./type-coder.js";

export class OperationTypeCoder extends Coder {
export class OperationTypeCoder extends TypeCoder {
constructor(requirement, requestMethod) {
super(requirement);

Expand Down Expand Up @@ -77,7 +77,7 @@ export class OperationTypeCoder extends Coder {
}

// eslint-disable-next-line max-statements
write(script) {
writeCode(script) {
// eslint-disable-next-line no-param-reassign
script.comments = READ_ONLY_COMMENTS;

Expand Down Expand Up @@ -115,7 +115,7 @@ export class OperationTypeCoder extends Coder {
? "never"
: new SchemaTypeCoder(bodyRequirement).write(script);

const responseType = new ResponseTypeCoder(
const responseType = new ResponsesTypeCoder(
this.requirement.get("responses"),
this.requirement.get("produces")?.data ??
this.requirement.specification?.rootRequirement?.get("produces")?.data,
Expand Down
6 changes: 3 additions & 3 deletions src/typescript-generator/parameters-type-coder.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import nodePath from "node:path";

import { Coder } from "./coder.js";
import { SchemaTypeCoder } from "./schema-type-coder.js";
import { TypeCoder } from "./type-coder.js";

export class ParametersTypeCoder extends Coder {
export class ParametersTypeCoder extends TypeCoder {
constructor(requirement, placement) {
super(requirement);

Expand All @@ -14,7 +14,7 @@ export class ParametersTypeCoder extends Coder {
return super.names("parameters");
}

write(script) {
writeCode(script) {
const typeDefinitions = (this.requirement?.data ?? [])
.filter((parameter) => parameter.in === this.placement)
.map((parameter, index) => {
Expand Down
59 changes: 13 additions & 46 deletions src/typescript-generator/response-type-coder.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,16 @@
import { Coder } from "./coder.js";
import { printObject, printObjectWithoutQuotes } from "./printers.js";
import { printObject } from "./printers.js";
import { SchemaTypeCoder } from "./schema-type-coder.js";
import { TypeCoder } from "./type-coder.js";

export class ResponseTypeCoder extends Coder {
export class ResponseTypeCoder extends TypeCoder {
constructor(requirement, openApi2MediaTypes = []) {
super(requirement);

this.openApi2MediaTypes = openApi2MediaTypes;
}

typeForDefaultStatusCode(listedStatusCodes) {
const definedStatusCodes = listedStatusCodes.filter(
(key) => key !== "default",
);

if (definedStatusCodes.length === 0) {
return "[statusCode in HttpStatusCode]";
}

return `[statusCode in Exclude<HttpStatusCode, ${definedStatusCodes.join(
" | ",
)}>]`;
}

normalizeStatusCode(statusCode) {
if (statusCode === "default") {
return this.typeForDefaultStatusCode(Object.keys(this.requirement.data));
}

return statusCode;
names() {
return super.names(this.requirement.data.$ref.split("/").at(-1));
}

buildContentObjectType(script, response) {
Expand Down Expand Up @@ -85,30 +67,15 @@ export class ResponseTypeCoder extends Coder {
return requiredHeaders.length === 0 ? "never" : requiredHeaders.join(" | ");
}

buildResponseObjectType(script) {
return printObjectWithoutQuotes(
this.requirement.map((response, responseCode) => [
this.normalizeStatusCode(responseCode),
`{
headers: ${this.printHeaders(script, response)};
requiredHeaders: ${this.printRequiredHeaders(response)};
content: ${this.printContentObjectType(script, response)};
}`,
]),
);
modulePath() {
return `components/${this.requirement.data.$ref.split("/").at(-1)}.ts`;
}

write(script) {
script.importSharedType("ResponseBuilderFactory");

const text = `ResponseBuilderFactory<${this.buildResponseObjectType(
script,
)}>`;

if (text.includes("HttpStatusCode")) {
script.importSharedType("HttpStatusCode");
}

return text;
writeCode(script) {
return `{
headers: ${this.printHeaders(script, this.requirement)};
requiredHeaders: ${this.printRequiredHeaders(this.requirement)};
content: ${this.printContentObjectType(script, this.requirement)};
}`;
}
}
56 changes: 56 additions & 0 deletions src/typescript-generator/responses-type-coder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { printObjectWithoutQuotes } from "./printers.js";
import { ResponseTypeCoder } from "./response-type-coder.js";
import { TypeCoder } from "./type-coder.js";

export class ResponsesTypeCoder extends TypeCoder {
constructor(requirement, openApi2MediaTypes = []) {
super(requirement);

this.openApi2MediaTypes = openApi2MediaTypes;
}

typeForDefaultStatusCode(listedStatusCodes) {
const definedStatusCodes = listedStatusCodes.filter(
(key) => key !== "default",
);

if (definedStatusCodes.length === 0) {
return "[statusCode in HttpStatusCode]";
}

return `[statusCode in Exclude<HttpStatusCode, ${definedStatusCodes.join(
" | ",
)}>]`;
}

normalizeStatusCode(statusCode) {
if (statusCode === "default") {
return this.typeForDefaultStatusCode(Object.keys(this.requirement.data));
}

return statusCode;
}

buildResponseObjectType(script) {
return printObjectWithoutQuotes(
this.requirement.map((response, responseCode) => [
this.normalizeStatusCode(responseCode),
new ResponseTypeCoder(response, this.openApi2MediaTypes).write(script),
]),
);
}

writeCode(script) {
script.importSharedType("ResponseBuilderFactory");

const text = `ResponseBuilderFactory<${this.buildResponseObjectType(
script,
)}>`;

if (text.includes("HttpStatusCode")) {
script.importSharedType("HttpStatusCode");
}

return text;
}
}
6 changes: 1 addition & 5 deletions src/typescript-generator/schema-coder.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ export class SchemaCoder extends Coder {
return `components/${this.requirement.data.$ref.split("/").at(-1)}.ts`;
}

write(script) {
if (this.requirement.isReference) {
return script.import(this);
}

writeCode(script) {
const { type } = this.requirement.data;

if (type === "object") {
Expand Down
10 changes: 3 additions & 7 deletions src/typescript-generator/schema-type-coder.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Coder } from "./coder.js";
import { TypeCoder } from "./type-coder.js";

export class SchemaTypeCoder extends Coder {
export class SchemaTypeCoder extends TypeCoder {
names() {
return super.names(this.requirement.data.$ref.split("/").at(-1));
}
Expand Down Expand Up @@ -115,13 +115,9 @@ export class SchemaTypeCoder extends Coder {
return `components/${this.requirement.data.$ref.split("/").at(-1)}.ts`;
}

write(script) {
writeCode(script) {
// script.comments = READ_ONLY_COMMENTS;

if (this.requirement.isReference) {
return script.importType(this);
}

const { allOf, anyOf, oneOf, type } = this.requirement.data;

if (allOf ?? anyOf ?? oneOf) {
Expand Down
11 changes: 11 additions & 0 deletions src/typescript-generator/type-coder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Coder } from "./coder.js";

export class TypeCoder extends Coder {
write(script) {
if (this.requirement?.isReference) {
return script.importType(this);
}

return this.writeCode(script);
}
}

0 comments on commit 9762407

Please sign in to comment.