Skip to content
This repository was archived by the owner on Sep 27, 2024. It is now read-only.

Commit 1be9355

Browse files
committed
update
1 parent 0b5ab03 commit 1be9355

24 files changed

+237
-209
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@omer-x/ts-openapi-interface-generator",
3-
"version": "0.2.0",
3+
"version": "1.0.0",
44
"description": "OpenAPI interface generator for TypeScript",
55
"keywords": [
66
"typescript",

src/core/arguments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import yargs from "yargs";
22

33
const argv = yargs.option("output", {
44
alias: "o",
5-
describe: "Specify the output directry",
5+
describe: "Specify the output directory",
66
type: "string",
77
demandOption: false,
88
}).argv;

src/core/file.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
3+
4+
export default async function createFile(content: string, fileName: string, outputDir: string, targetFolder = ".") {
5+
const targetPath = path.resolve(outputDir, targetFolder);
6+
await fs.mkdir(targetPath, { recursive: true });
7+
const filePath = path.resolve(targetPath, fileName);
8+
await fs.writeFile(filePath, content);
9+
}

src/core/interface.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/core/openapi.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
type SchemaComponent = {
2-
type: "object" | "unknown",
3-
properties: Record<string, SchemaDefinition>,
4-
required?: string[],
5-
};
6-
71
export type SchemaDefinition = {
82
type?: "string" | "number" | "array" | "object",
93
format?: "date" | "unknown",
@@ -15,18 +9,24 @@ export type SchemaDefinition = {
159
enum?: (string | null)[],
1610
};
1711

18-
export type PathParameter = {
12+
type SchemaComponent = {
13+
type: "object" | "unknown",
14+
properties: Record<string, SchemaDefinition>,
15+
required?: string[],
16+
};
17+
18+
export type OperationParameter = {
1919
in: "path" | "query" | "header" | "cookie",
2020
name: string,
2121
description: string,
2222
required: boolean,
2323
schema: SchemaDefinition,
2424
};
2525

26-
export type RequestBody = {
26+
type RequestBody = {
2727
description: string,
2828
required: boolean,
29-
content: Record<ContentType, ResponseContent>,
29+
content: Content,
3030
};
3131

3232
type ResponseContent = {
@@ -35,33 +35,29 @@ type ResponseContent = {
3535

3636
type ContentType = "application/json" | "text/plain";
3737

38-
export type HttpResponse = {
38+
export type Content = Record<ContentType, ResponseContent>;
39+
40+
type HttpResponse = {
3941
description: string,
40-
content?: Record<ContentType, ResponseContent>,
42+
content?: Content,
4143
};
4244

4345
type HttpCode = "200" | "404";
4446

45-
export type ApiPath = {
46-
summary: string,
47+
export type Operation = {
4748
operationId: string,
48-
parameters?: PathParameter[],
49+
summary: string,
50+
description: string,
51+
parameters?: OperationParameter[],
4952
requestBody?: RequestBody,
5053
responses: Record<HttpCode, HttpResponse>,
5154
};
5255

53-
export type HttpMethod = "get" | "post" | "patch" | "put" | "delete";
56+
type HttpMethod = "get" | "post" | "patch" | "put" | "delete";
5457

55-
type OpenApiSpecification = {
58+
export type OpenAPI = {
5659
components: {
5760
schemas: Record<string, SchemaComponent>,
5861
},
59-
paths: Record<string, Record<HttpMethod, ApiPath>>,
62+
paths: Record<string, Record<HttpMethod, Operation>>,
6063
};
61-
62-
export async function getSwaggerJSON(base: string, specs?: string) {
63-
const url = new URL(specs ?? "/swagger", base);
64-
const response = await fetch(url);
65-
const data = await response.json();
66-
return data as OpenApiSpecification;
67-
}

src/core/operation.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/core/parameters.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/core/renderers/interface.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Handlebars from "handlebars";
2+
import type { OperationTemplate } from "~/core/resolvers/operation";
3+
import interfaceTemplate from "~/templates/interface.hbs";
4+
5+
type InterfaceTemplate = {
6+
baseUrl: string,
7+
schemas: string[],
8+
operations: OperationTemplate[],
9+
};
10+
11+
export default function generateInterface(baseUrl: string, schemas: string[], operations: OperationTemplate[]) {
12+
const template = Handlebars.compile<InterfaceTemplate>(interfaceTemplate);
13+
return template({
14+
baseUrl,
15+
schemas,
16+
operations,
17+
});
18+
}

src/core/renderers/operation.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Handlebars from "handlebars";
2+
import operationTemplate from "~/templates/operation.hbs";
3+
4+
Handlebars.registerHelper("hasRequestBody", (input: string[]) => {
5+
return input.includes("requestBody");
6+
});
7+
8+
Handlebars.registerPartial("operation", operationTemplate);

src/core/renderers/parameter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Handlebars from "handlebars";
2+
import searchParamTemplate from "~/templates/search-param.hbs";
3+
4+
Handlebars.registerPartial("searchParam", searchParamTemplate);

src/core/renderers/schema.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { ModelProperty } from "~/core/resolvers/property";
2+
import getTemplate from "~/core/template";
3+
import schemaTemplate from "~/templates/schema.hbs";
4+
5+
type SchemaTemplate = {
6+
importedSchemas: string[],
7+
name: string,
8+
properties: ModelProperty[],
9+
};
10+
11+
export default function generateSchema(name: string, properties: ModelProperty[], importedSchemas: string[]) {
12+
const template = getTemplate<SchemaTemplate>(schemaTemplate);
13+
return template({
14+
name,
15+
properties,
16+
importedSchemas,
17+
});
18+
}

src/core/resolvers/content.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Content } from "~/core/openapi";
2+
3+
export default function getContentSchema(content: Content) {
4+
return content["application/json"].schema;
5+
}

src/core/resolvers/enpoint.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { OpenAPI } from "~/core/openapi";
2+
3+
type ResolvedEndpoint = {
4+
method: keyof OpenAPI["paths"][string],
5+
path: keyof OpenAPI["paths"],
6+
operation: OpenAPI["paths"][string]["get"],
7+
};
8+
9+
export default function resolveEndpoints(paths: OpenAPI["paths"]) {
10+
return Object.entries(paths).map(([path, methods]) => {
11+
return Object.entries(methods).map<ResolvedEndpoint>(([method, operation]) => ({
12+
method: method as keyof OpenAPI["paths"][string],
13+
path,
14+
operation,
15+
}));
16+
}).flat();
17+
}

src/core/imported-schema.ts renamed to src/core/resolvers/imported-schema.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import type { OpenAPI, Operation, SchemaDefinition } from "~/core/openapi";
2+
import getContentSchema from "./content";
3+
import resolveEndpoints from "./enpoint";
14
import { filterGenericSchemas, resolveSchema, simplifySchema } from "./schema-definition";
2-
import type { ApiPath, HttpMethod, HttpResponse, RequestBody, SchemaDefinition } from "./openapi";
35

4-
function resolveRequestSchemas(requestBody?: RequestBody) {
6+
function resolveRequestSchemas(requestBody: Operation["requestBody"]) {
57
if (!requestBody) return [];
6-
return [resolveSchema(requestBody.content["application/json"].schema)];
8+
return [resolveSchema(getContentSchema(requestBody.content))];
79
}
810

9-
function resolveResponseSchemas(responses: Record<string, HttpResponse>) {
11+
function resolveResponseSchemas(responses: Operation["responses"]) {
1012
return Object.values(responses).map(response => {
1113
const resolvedSchemas = Object.values(response.content ?? {}).map(content => {
1214
return simplifySchema(resolveSchema(content.schema));
@@ -15,15 +17,11 @@ function resolveResponseSchemas(responses: Record<string, HttpResponse>) {
1517
}).flat();
1618
}
1719

18-
export function resolveSchemas(paths: Record<string, Record<HttpMethod, ApiPath>>) {
19-
const collection = Object.values(paths).map(methods => {
20-
return Object.values(methods).map(path => {
21-
return [
22-
...resolveRequestSchemas(path.requestBody),
23-
...resolveResponseSchemas(path.responses),
24-
];
25-
}).flat();
26-
}).flat();
20+
export function resolveSchemas(paths: OpenAPI["paths"]) {
21+
const collection = resolveEndpoints(paths).map(({ operation }) => ([
22+
...resolveRequestSchemas(operation.requestBody),
23+
...resolveResponseSchemas(operation.responses),
24+
])).flat();
2725
return Array.from(new Set(collection)).toSorted();
2826
}
2927

src/core/resolvers/operation-param.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Operation, OperationParameter } from "~/core/openapi";
2+
import { resolveSchema } from "~/core/resolvers/schema-definition";
3+
import getContentSchema from "./content";
4+
5+
function resolveParam(param: OperationParameter, typescript: boolean) {
6+
if (!typescript) return param.name;
7+
return `${param.name}${param.required ? "" : "?"}: ${resolveSchema(param.schema)}`;
8+
}
9+
10+
function resolveRequestBody(body: Exclude<Operation["requestBody"], undefined>, typescript: boolean) {
11+
if (!typescript) return "requestBody";
12+
return `requestBody${body.required ? "" : "?"}: ${resolveSchema(getContentSchema(body.content))}`;
13+
}
14+
15+
function sortRequiredParamsFirst(paramA: OperationParameter, paramB: OperationParameter) {
16+
if (paramA.required === paramB.required) return 0;
17+
return paramA.required ? -1 : 1;
18+
}
19+
20+
export function resolveOperationParams(operation: Operation, typescript: boolean) {
21+
const resolvedParams = (operation.parameters ?? [])
22+
.filter(param => param.in === "path" || param.in === "query")
23+
.toSorted(sortRequiredParamsFirst)
24+
.map(p => resolveParam(p, typescript));
25+
const collection = [
26+
...resolvedParams,
27+
];
28+
if (operation.requestBody) collection.push(resolveRequestBody(operation.requestBody, typescript));
29+
return collection;
30+
}

0 commit comments

Comments
 (0)