diff --git a/README.md b/README.md index 3c767915..347ee079 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ import type * as Types from "@himenon/openapi-typescript-code-generator/types"; const main = () => { const codeGenerator = new CodeGenerator("your/openapi/spec.yml"); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, option: {}, }; @@ -86,8 +86,8 @@ import type * as Types from "@himenon/openapi-typescript-code-generator/types"; const main = () => { const codeGenerator = new CodeGenerator("your/openapi/spec.yml"); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, option: {}, }; @@ -304,7 +304,7 @@ It provides parameters extracted from OpenAPI Schema. #### getAdditionalTypeDefinitionCustomCodeGenerator -This is a type definition file for `Templates.ApiClient`. The reason it is not included in `generateTypeDefinition` is that you may not use the type definition generated by this function depending on your usage. +This is a type definition file for `Templates.FunctionalApiClient`. The reason it is not included in `generateTypeDefinition` is that you may not use the type definition generated by this function depending on your usage. ※ The reason it is not included in `generateTypeDefinition` is that you may not use the type definitions generated by this function depending on your application. diff --git a/docs/ja/README-ja.md b/docs/ja/README-ja.md index a4d9283f..474e6c68 100644 --- a/docs/ja/README-ja.md +++ b/docs/ja/README-ja.md @@ -52,8 +52,8 @@ import type * as Types from "@himenon/openapi-typescript-code-generator/types"; const main = () => { const codeGenerator = new CodeGenerator("your/openapi/spec.yml"); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, option: {}, }; @@ -80,8 +80,8 @@ import type * as Types from "@himenon/openapi-typescript-code-generator/types"; const main = () => { const codeGenerator = new CodeGenerator("your/openapi/spec.yml"); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, option: {}, }; @@ -298,7 +298,7 @@ OpenAPI Schema から抽出したパラメーターを取得できます。 #### getAdditionalTypeDefinitionCustomCodeGenerator -`Templates.ApiClient`向けの型定義ファイルです。`generateTypeDefinition`に含めていない理由は、用途によってこの関数が生成する型定義を利用しない可能性があるためです。 +`Templates.FunctionalApiClient`向けの型定義ファイルです。`generateTypeDefinition`に含めていない理由は、用途によってこの関数が生成する型定義を利用しない可能性があるためです。 ※ 将来的に`Templates`の API に移動する予定です。 diff --git a/example/codegen.ts b/example/codegen.ts index cbaaa6ba..adae8cfa 100644 --- a/example/codegen.ts +++ b/example/codegen.ts @@ -7,8 +7,8 @@ import * as Types from "../lib/types"; // = @himenon/openapi-typescript-code-gen const main = () => { const codeGenerator = new CodeGenerator("./spec/openapi.yml"); codeGenerator.validateOpenApiSchema({ logger: { displayLogLines: 1 } }); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, option: {}, }; diff --git a/example/readme-sample/split-typedef-and-api-client.ts b/example/readme-sample/split-typedef-and-api-client.ts index d75a37a5..9cda062e 100644 --- a/example/readme-sample/split-typedef-and-api-client.ts +++ b/example/readme-sample/split-typedef-and-api-client.ts @@ -7,8 +7,8 @@ import type * as Types from "@himenon/openapi-typescript-code-generator/types"; const main = () => { const codeGenerator = new CodeGenerator("your/openapi/spec.yml"); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, option: {}, }; diff --git a/example/readme-sample/typedef-and-api-client.ts b/example/readme-sample/typedef-and-api-client.ts index a28e89bb..afe2132a 100644 --- a/example/readme-sample/typedef-and-api-client.ts +++ b/example/readme-sample/typedef-and-api-client.ts @@ -7,8 +7,8 @@ import type * as Types from "@himenon/openapi-typescript-code-generator/types"; const main = () => { const codeGenerator = new CodeGenerator("your/openapi/spec.yml"); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, option: {}, }; diff --git a/package.json b/package.json index ac8df43a..5b1c2960 100644 --- a/package.json +++ b/package.json @@ -40,13 +40,16 @@ "scripts": { "build": "pnpm ts ./scripts/build.ts", "clean": "pnpm ts ./scripts/clean.ts", + "format": "run-s format:code format:code:eslint", "format:code": "prettier \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md,html}\" --write", "format:code:eslint": "eslint \"**/*.{ts,tsx}\" --fix", "lerna:version:up": "lerna version --yes", "release:github:registry": "pnpm publish --no-git-checks --registry https://npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}", "release:npm:registry": "pnpm publish --no-git-checks", - "test": "run-p test:depcruise test:jest test:code:gen test:snapshot", - "test:code:gen": "pnpm ts ./scripts/testCodeGen.ts", + "test": "run-p test:depcruise test:jest test:code:gen:* test:snapshot", + "test:code:gen": "run-p test:code:gen:*", + "test:code:gen:class": "pnpm ts ./scripts/testCodeGenWithClass.ts", + "test:code:gen:function": "pnpm ts ./scripts/testCodeGenWithFunctional.ts", "test:depcruise": "depcruise --validate .dependency-cruiser.js src", "test:eslint": "eslint \"src/**/*.{ts,tsx}\"", "test:jest": "jest -c ./jest.config.js --collect-coverage", diff --git a/scripts/testCodeGen.ts b/scripts/testCodeGenWithClass.ts similarity index 50% rename from scripts/testCodeGen.ts rename to scripts/testCodeGenWithClass.ts index 83443f1a..c010a92a 100644 --- a/scripts/testCodeGen.ts +++ b/scripts/testCodeGenWithClass.ts @@ -1,50 +1,57 @@ -import * as Writer from "./writer"; +import * as Writer from "./writer/Class"; const main = () => { - Writer.generateTypedefCodeOnly("test/api.test.domain/index.yml", "test/code/typedef-only/api.test.domain.ts", true); - Writer.generateTypedefCodeOnly("test/infer.domain/index.yml", "test/code/typedef-only/infer.domain.ts", false); - Writer.generateTypedefCodeOnly("test/json.properties/index.yml", "test/code/typedef-only/json.properties.ts", false); + Writer.generateTypedefCodeOnly("test/api.test.domain/index.yml", "test/code/class/typedef-only/api.test.domain.ts", true); + Writer.generateTypedefCodeOnly("test/infer.domain/index.yml", "test/code/class/typedef-only/infer.domain.ts", false); + Writer.generateTypedefCodeOnly("test/json.properties/index.yml", "test/code/class/typedef-only/json.properties.ts", false); - Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/api.test.domain.ts", true, { sync: false }); - Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/sync-api.test.domain.ts", true, { sync: true }); - Writer.generateTemplateCodeOnly("test/infer.domain/index.yml", "test/code/template-only/infer.domain.ts", false, { sync: true }); + Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/class/template-only/api.test.domain.ts", true, { sync: false }); + Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/class/template-only/sync-api.test.domain.ts", true, { + sync: true, + }); + Writer.generateTemplateCodeOnly("test/infer.domain/index.yml", "test/code/class/template-only/infer.domain.ts", false, { sync: true }); - Writer.generateTypedefWithTemplateCode("test/api.v2.domain/index.yml", "test/code/typedef-with-template/api.v2.domain.ts", false, { + Writer.generateTypedefWithTemplateCode("test/api.v2.domain/index.yml", "test/code/class/typedef-with-template/api.v2.domain.ts", false, { sync: false, }); - Writer.generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/api.test.domain.ts", true, { + Writer.generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/class/typedef-with-template/api.test.domain.ts", true, { sync: false, }); - Writer.generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/sync-api.test.domain.ts", true, { - sync: true, - }); - Writer.generateTypedefWithTemplateCode("test/infer.domain/index.yml", "test/code/typedef-with-template/infer.domain.ts", false, { + Writer.generateTypedefWithTemplateCode( + "test/api.test.domain/index.yml", + "test/code/class/typedef-with-template/sync-api.test.domain.ts", + true, + { + sync: true, + }, + ); + Writer.generateTypedefWithTemplateCode("test/infer.domain/index.yml", "test/code/class/typedef-with-template/infer.domain.ts", false, { sync: false, }); - Writer.generateTypedefWithTemplateCode("test/ref.access/index.yml", "test/code/typedef-with-template/ref-access.ts", false, { + Writer.generateTypedefWithTemplateCode("test/ref.access/index.yml", "test/code/class/typedef-with-template/ref-access.ts", false, { sync: false, }); - Writer.generateTypedefWithTemplateCode("test/remote.ref.access/v1.yml", "test/code/typedef-with-template/remote-ref-access.ts", false, { + Writer.generateTypedefWithTemplateCode("test/remote.ref.access/v1.yml", "test/code/class/typedef-with-template/remote-ref-access.ts", false, { sync: false, }); - Writer.generateTypedefWithTemplateCode("test/kubernetes/openapi-v1.18.5.json", "test/code/kubernetes/client-v1.18.5.ts", false, { + Writer.generateTypedefWithTemplateCode("test/kubernetes/openapi-v1.18.5.json", "test/code/class/kubernetes/client-v1.18.5.ts", false, { sync: false, }); - Writer.generateTypedefWithTemplateCode("test/argo-rollout/index.json", "test/code/argo-rollout/client.ts", false, { + Writer.generateTypedefWithTemplateCode("test/argo-rollout/index.json", "test/code/class/argo-rollout/client.ts", false, { sync: false, }); - Writer.generateTypedefWithTemplateCode("test/unknown.schema.domain/index.yml", "test/code/unknown.schema.domain/client.ts", false, { + Writer.generateTypedefWithTemplateCode("test/unknown.schema.domain/index.yml", "test/code/class/unknown.schema.domain/client.ts", false, { sync: false, }); - Writer.generateSplitCode("test/api.test.domain/index.yml", "test/code/split"); - Writer.generateSplitCode("test/multi-type.test.domain/index.yml", "test/code/mulit-type-test.domain"); + Writer.generateSplitCode("test/api.test.domain/index.yml", "test/code/class/split"); + Writer.generateSplitCode("test/multi-type.test.domain/index.yml", "test/code/class/mulit-type-test.domain"); - Writer.generateParameter("test/api.test.domain/index.yml", "test/code/parameter/api.test.domain.json"); - Writer.generateParameter("test/infer.domain/index.yml", "test/code/parameter/infer.domain.json"); + Writer.generateParameter("test/api.test.domain/index.yml", "test/code/class/parameter/api.test.domain.json"); + Writer.generateParameter("test/infer.domain/index.yml", "test/code/class/parameter/infer.domain.json"); - Writer.generateFormatTypeCode("test/format.domain/index.yml", "test/code/format.domain/code.ts"); + Writer.generateFormatTypeCode("test/format.domain/index.yml", "test/code/class/format.domain/code.ts"); }; main(); diff --git a/scripts/testCodeGenWithFunctional.ts b/scripts/testCodeGenWithFunctional.ts new file mode 100644 index 00000000..855e66ab --- /dev/null +++ b/scripts/testCodeGenWithFunctional.ts @@ -0,0 +1,74 @@ +import * as Writer from "./writer/Functional"; + +const main = () => { + Writer.generateTypedefCodeOnly("test/api.test.domain/index.yml", "test/code/functional/typedef-only/api.test.domain.ts", true); + Writer.generateTypedefCodeOnly("test/infer.domain/index.yml", "test/code/functional/typedef-only/infer.domain.ts", false); + Writer.generateTypedefCodeOnly("test/json.properties/index.yml", "test/code/functional/typedef-only/json.properties.ts", false); + + Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/functional/template-only/api.test.domain.ts", true, { + sync: false, + }); + Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/functional/template-only/sync-api.test.domain.ts", true, { + sync: true, + }); + Writer.generateTemplateCodeOnly("test/infer.domain/index.yml", "test/code/functional/template-only/infer.domain.ts", false, { sync: true }); + + Writer.generateTypedefWithTemplateCode("test/api.v2.domain/index.yml", "test/code/functional/typedef-with-template/api.v2.domain.ts", false, { + sync: false, + }); + Writer.generateTypedefWithTemplateCode( + "test/api.test.domain/index.yml", + "test/code/functional/typedef-with-template/api.test.domain.ts", + true, + { + sync: false, + }, + ); + Writer.generateTypedefWithTemplateCode( + "test/api.test.domain/index.yml", + "test/code/functional/typedef-with-template/sync-api.test.domain.ts", + true, + { + sync: true, + }, + ); + Writer.generateTypedefWithTemplateCode("test/infer.domain/index.yml", "test/code/functional/typedef-with-template/infer.domain.ts", false, { + sync: false, + }); + + Writer.generateTypedefWithTemplateCode("test/ref.access/index.yml", "test/code/functional/typedef-with-template/ref-access.ts", false, { + sync: false, + }); + Writer.generateTypedefWithTemplateCode( + "test/remote.ref.access/v1.yml", + "test/code/functional/typedef-with-template/remote-ref-access.ts", + false, + { + sync: false, + }, + ); + Writer.generateTypedefWithTemplateCode("test/kubernetes/openapi-v1.18.5.json", "test/code/functional/kubernetes/client-v1.18.5.ts", false, { + sync: false, + }); + Writer.generateTypedefWithTemplateCode("test/argo-rollout/index.json", "test/code/functional/argo-rollout/client.ts", false, { + sync: false, + }); + Writer.generateTypedefWithTemplateCode( + "test/unknown.schema.domain/index.yml", + "test/code/functional/unknown.schema.domain/client.ts", + false, + { + sync: false, + }, + ); + + Writer.generateSplitCode("test/api.test.domain/index.yml", "test/code/functional/split"); + Writer.generateSplitCode("test/multi-type.test.domain/index.yml", "test/code/functional/mulit-type-test.domain"); + + Writer.generateParameter("test/api.test.domain/index.yml", "test/code/functional/parameter/api.test.domain.json"); + Writer.generateParameter("test/infer.domain/index.yml", "test/code/functional/parameter/infer.domain.json"); + + Writer.generateFormatTypeCode("test/format.domain/index.yml", "test/code/functional/format.domain/code.ts"); +}; + +main(); diff --git a/scripts/watch.ts b/scripts/watch.ts index c91b4be2..aca26350 100644 --- a/scripts/watch.ts +++ b/scripts/watch.ts @@ -14,7 +14,7 @@ const main = async () => { console.log((await shell("pnpm build", cwd)).stdout); console.log((await shell("pnpm run test:code:gen", cwd)).stdout); - chokidar.watch("./src", {}).on("change", async path => { + chokidar.watch("./src", { ignored: ["src/meta.ts"] }).on("change", async path => { console.log(`Watch Change file ... ${path}`); try { console.log((await shell("pnpm build", cwd)).stdout); diff --git a/scripts/writer/index.ts b/scripts/writer/Class.ts similarity index 90% rename from scripts/writer/index.ts rename to scripts/writer/Class.ts index 13a5dd25..7d4abfeb 100644 --- a/scripts/writer/index.ts +++ b/scripts/writer/Class.ts @@ -26,7 +26,7 @@ export const generateTemplateCodeOnly = ( inputFilename: string, outputFilename: string, isValidate: boolean, - option: Templates.ApiClient.Option, + option: Templates.ClassApiClient.Option, ): void => { const codeGenerator = new CodeGenerator(inputFilename); if (isValidate) { @@ -35,8 +35,8 @@ export const generateTemplateCodeOnly = ( }); } - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.ClassApiClient.generator, option: option, }; @@ -49,7 +49,7 @@ export const generateTypedefWithTemplateCode = ( inputFilename: string, outputFilename: string, isValidate: boolean, - option: Templates.ApiClient.Option, + option: Templates.ClassApiClient.Option, ): void => { const codeGenerator = new CodeGenerator(inputFilename); if (isValidate) { @@ -61,7 +61,7 @@ export const generateTypedefWithTemplateCode = ( const code = codeGenerator.generateTypeDefinition([ codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(), { - generator: Templates.ApiClient.generator, + generator: Templates.ClassApiClient.generator, option: option, }, ]); @@ -72,8 +72,8 @@ export const generateTypedefWithTemplateCode = ( export const generateSplitCode = (inputFilename: string, outputDir: string) => { const codeGenerator = new CodeGenerator(inputFilename); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.ClassApiClient.generator, option: { sync: false, additionalMethodComment: true }, }; @@ -147,8 +147,8 @@ export const generateFormatTypeCode = (inputFilename: string, outputFilename: st }; const codeGenerator = new CodeGenerator(inputFilename, option); - const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { - generator: Templates.ApiClient.generator, + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.ClassApiClient.generator, option: {}, }; diff --git a/scripts/writer/Functional.ts b/scripts/writer/Functional.ts new file mode 100644 index 00000000..18e3078f --- /dev/null +++ b/scripts/writer/Functional.ts @@ -0,0 +1,161 @@ +import * as fs from "fs"; +import { posix as path } from "path"; + +import { CodeGenerator, Option as CodeGeneratorOption } from "../../lib"; +import * as Templates from "../../lib/templates"; +import type * as Types from "../../lib/types"; + +const writeText = (filename: string, text: string): void => { + fs.mkdirSync(path.dirname(filename), { recursive: true }); + fs.writeFileSync(filename, text, { encoding: "utf-8" }); + console.log(`Generate Code : ${filename}`); +}; + +export const generateTypedefCodeOnly = (inputFilename: string, outputFilename: string, isValidate: boolean) => { + const codeGenerator = new CodeGenerator(inputFilename); + if (isValidate) { + codeGenerator.validateOpenApiSchema({ + logger: { displayLogLines: 1 }, + }); + } + const code = codeGenerator.generateTypeDefinition(); + writeText(outputFilename, code); +}; + +export const generateTemplateCodeOnly = ( + inputFilename: string, + outputFilename: string, + isValidate: boolean, + option: Templates.FunctionalApiClient.Option, +): void => { + const codeGenerator = new CodeGenerator(inputFilename); + if (isValidate) { + codeGenerator.validateOpenApiSchema({ + logger: { displayLogLines: 1 }, + }); + } + + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, + option: option, + }; + + const code = codeGenerator.generateCode([apiClientGeneratorTemplate]); + + writeText(outputFilename, code); +}; + +export const generateTypedefWithTemplateCode = ( + inputFilename: string, + outputFilename: string, + isValidate: boolean, + option: Templates.FunctionalApiClient.Option, +): void => { + const codeGenerator = new CodeGenerator(inputFilename); + if (isValidate) { + codeGenerator.validateOpenApiSchema({ + logger: { displayLogLines: 1 }, + }); + } + + const code = codeGenerator.generateTypeDefinition([ + codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(), + { + generator: Templates.FunctionalApiClient.generator, + option: option, + }, + ]); + + writeText(outputFilename, code); +}; + +export const generateSplitCode = (inputFilename: string, outputDir: string) => { + const codeGenerator = new CodeGenerator(inputFilename); + + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, + option: { sync: false, additionalMethodComment: true }, + }; + + const typeDefCode = codeGenerator.generateTypeDefinition(); + const apiClientCode = codeGenerator.generateCode([ + { + generator: () => { + return [`import { Schemas } from "./types";`]; + }, + }, + codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(), + apiClientGeneratorTemplate, + ]); + + writeText(path.join(outputDir, "types.ts"), typeDefCode); + writeText(path.join(outputDir, "apiClient.ts"), apiClientCode); +}; + +export const generateParameter = (inputFilename: string, outputFilename: string) => { + const codeGenerator = new CodeGenerator(inputFilename); + writeText(outputFilename, JSON.stringify(codeGenerator.getCodeGeneratorParamsArray(), null, 2)); +}; + +export const generateFormatTypeCode = (inputFilename: string, outputFilename: string) => { + const option: CodeGeneratorOption = { + convertOption: { + formatConversions: [ + { + selector: { + format: "binary", + }, + output: { + type: ["Blob"], + }, + }, + { + selector: { + format: "int-or-string", + }, + output: { + type: ["number", "string"], + }, + }, + { + selector: { + format: "custom-type", + }, + output: { + type: ["CustomType"], + }, + }, + { + selector: { + format: "date-time", + }, + output: { + type: ["Date"], + }, + }, + { + selector: { + format: "A-and-B", + }, + output: { + type: ["A", "B"], + multiType: "allOf", + }, + }, + ], + }, + }; + const codeGenerator = new CodeGenerator(inputFilename, option); + + const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator = { + generator: Templates.FunctionalApiClient.generator, + option: {}, + }; + + const code = codeGenerator.generateTypeDefinition([ + codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(), + apiClientGeneratorTemplate, + ]); + + writeText(outputFilename, code); +}; diff --git a/src/code-templates/class-api-client/ApiClientArgument.ts b/src/code-templates/_shared/ApiClientArgument.ts similarity index 100% rename from src/code-templates/class-api-client/ApiClientArgument.ts rename to src/code-templates/_shared/ApiClientArgument.ts diff --git a/src/code-templates/class-api-client/ApiClientClass/ApiClientInterface.ts b/src/code-templates/_shared/ApiClientInterface.ts similarity index 97% rename from src/code-templates/class-api-client/ApiClientClass/ApiClientInterface.ts rename to src/code-templates/_shared/ApiClientInterface.ts index 1480ee93..0a5c71ab 100644 --- a/src/code-templates/class-api-client/ApiClientClass/ApiClientInterface.ts +++ b/src/code-templates/_shared/ApiClientInterface.ts @@ -1,8 +1,8 @@ import ts from "typescript"; -import type { TsGenerator } from "../../../api"; -import type { CodeGenerator } from "../../../types"; -import type { Option } from "../types"; +import type { TsGenerator } from "../../api"; +import type { CodeGenerator } from "../../types"; +import type { Option } from "./types"; const httpMethodList: string[] = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"]; diff --git a/src/code-templates/class-api-client/ApiClientClass/MethodBody/CallRequest.ts b/src/code-templates/_shared/MethodBody/CallRequest.ts similarity index 66% rename from src/code-templates/class-api-client/ApiClientClass/MethodBody/CallRequest.ts rename to src/code-templates/_shared/MethodBody/CallRequest.ts index 1b89a73c..7a03b19e 100644 --- a/src/code-templates/class-api-client/ApiClientClass/MethodBody/CallRequest.ts +++ b/src/code-templates/_shared/MethodBody/CallRequest.ts @@ -1,8 +1,9 @@ import ts from "typescript"; -import type { TsGenerator } from "../../../../api"; -import type { CodeGenerator } from "../../../../types"; -import * as Utils from "../../utils"; +import type { TsGenerator } from "../../../api"; +import type { CodeGenerator } from "../../../types"; +import * as Utils from "../../class-api-client/utils"; +import type { MethodType } from "./types"; export interface Params { httpMethod: string; @@ -12,9 +13,13 @@ export interface Params { /** * this.apiClient.request("GET", url, requestBody, headers, queryParameters); */ -export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator.Params): ts.CallExpression => { +export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator.Params, methodType: MethodType): ts.CallExpression => { const { convertedParams } = params; - const expression = Utils.generateVariableIdentifier(factory, "this.apiClient.request"); + const apiClientVariableIdentifier: Record = { + class: "this.apiClient.request", + function: "apiClient.request", + }; + const expression = Utils.generateVariableIdentifier(factory, apiClientVariableIdentifier[methodType]); const argumentsArray = [ factory.StringLiteral.create({ text: params.operationParams.httpMethod.toUpperCase() }), factory.Identifier.create({ name: "url" }), diff --git a/src/code-templates/class-api-client/ApiClientClass/MethodBody/HeaderParameter.ts b/src/code-templates/_shared/MethodBody/HeaderParameter.ts similarity index 84% rename from src/code-templates/class-api-client/ApiClientClass/MethodBody/HeaderParameter.ts rename to src/code-templates/_shared/MethodBody/HeaderParameter.ts index 57ba2977..0134d75a 100644 --- a/src/code-templates/class-api-client/ApiClientClass/MethodBody/HeaderParameter.ts +++ b/src/code-templates/_shared/MethodBody/HeaderParameter.ts @@ -1,7 +1,7 @@ import ts from "typescript"; -import type { TsGenerator } from "../../../../api"; -import * as Utils from "../../utils"; +import type { TsGenerator } from "../../../api"; +import * as Utils from "../../class-api-client/utils"; export interface Params { variableName: string; diff --git a/src/code-templates/class-api-client/ApiClientClass/MethodBody/PathParameter.ts b/src/code-templates/_shared/MethodBody/PathParameter.ts similarity index 83% rename from src/code-templates/class-api-client/ApiClientClass/MethodBody/PathParameter.ts rename to src/code-templates/_shared/MethodBody/PathParameter.ts index 90b9c5b9..a8b34da5 100644 --- a/src/code-templates/class-api-client/ApiClientClass/MethodBody/PathParameter.ts +++ b/src/code-templates/_shared/MethodBody/PathParameter.ts @@ -1,9 +1,10 @@ import ts from "typescript"; -import type { TsGenerator } from "../../../../api"; -import type { CodeGenerator } from "../../../../types"; -import * as Utils from "../../utils"; -import { escapeText2 as escapeText } from "../../../../utils"; +import type { TsGenerator } from "../../../api"; +import type { CodeGenerator } from "../../../types"; +import * as Utils from "../../class-api-client/utils"; +import { escapeText2 as escapeText } from "../../../utils"; +import type { MethodType } from "./types"; export const isPathParameter = (params: any): params is CodeGenerator.PickedParameter => { return params.in === "path"; @@ -15,17 +16,24 @@ export const isPathParameter = (params: any): params is CodeGenerator.PickedPara const generateUrlVariableStatement = ( factory: TsGenerator.Factory.Type, urlTemplate: Utils.Params$TemplateExpression, + methodType: MethodType, ): ts.VariableStatement => { + const left: Record = { + class: factory.PropertyAccessExpression.create({ + name: "baseUrl", + expression: "this", + }), + function: factory.Identifier.create({ + name: "baseUrl", + }), + }; return factory.VariableStatement.create({ declarationList: factory.VariableDeclarationList.create({ declarations: [ factory.VariableDeclaration.create({ name: "url", initializer: factory.BinaryExpression.create({ - left: factory.PropertyAccessExpression.create({ - name: "baseUrl", - expression: "this", - }), + left: left[methodType], operator: "+", right: Utils.generateTemplateExpression(factory, urlTemplate), }), @@ -104,10 +112,11 @@ export const create = ( factory: TsGenerator.Factory.Type, requestUri: string, pathParameters: CodeGenerator.PickedParameter[], + methodType: MethodType, ): ts.VariableStatement => { if (pathParameters.length > 0) { const urlTemplate = generateUrlTemplateExpression(factory, requestUri, pathParameters); - return generateUrlVariableStatement(factory, urlTemplate); + return generateUrlVariableStatement(factory, urlTemplate, methodType); } - return generateUrlVariableStatement(factory, [{ type: "string", value: requestUri }]); + return generateUrlVariableStatement(factory, [{ type: "string", value: requestUri }], methodType); }; diff --git a/src/code-templates/class-api-client/ApiClientClass/MethodBody/QueryParameter.ts b/src/code-templates/_shared/MethodBody/QueryParameter.ts similarity index 93% rename from src/code-templates/class-api-client/ApiClientClass/MethodBody/QueryParameter.ts rename to src/code-templates/_shared/MethodBody/QueryParameter.ts index 827bd0d9..f5c52ca1 100644 --- a/src/code-templates/class-api-client/ApiClientClass/MethodBody/QueryParameter.ts +++ b/src/code-templates/_shared/MethodBody/QueryParameter.ts @@ -1,8 +1,8 @@ import ts from "typescript"; -import type { TsGenerator } from "../../../../api"; -import * as Utils from "../../../../utils"; -import * as UtilsExtra from "../../utils"; +import type { TsGenerator } from "../../../api"; +import * as Utils from "../../../utils"; +import * as UtilsExtra from "../../class-api-client/utils"; export interface Item { type: "string" | "variable"; diff --git a/src/code-templates/class-api-client/ApiClientClass/MethodBody/__tests__/PathParameter-test.ts b/src/code-templates/_shared/MethodBody/__tests__/PathParameter-test.ts similarity index 83% rename from src/code-templates/class-api-client/ApiClientClass/MethodBody/__tests__/PathParameter-test.ts rename to src/code-templates/_shared/MethodBody/__tests__/PathParameter-test.ts index 08a62c36..9aacdd82 100644 --- a/src/code-templates/class-api-client/ApiClientClass/MethodBody/__tests__/PathParameter-test.ts +++ b/src/code-templates/_shared/MethodBody/__tests__/PathParameter-test.ts @@ -2,28 +2,31 @@ import { EOL } from "os"; import ts from "typescript"; -import { TsGenerator } from "../../../../../api"; -import type { CodeGenerator } from "../../../../../types"; -import * as Utils from "../../../utils"; +import { TsGenerator } from "../../../../api"; +import type { CodeGenerator } from "../../../../types"; +import * as Utils from "../../../class-api-client/utils"; import * as PathParameter from "../PathParameter"; -const traverse = (expression: ts.Expression) => (context: Pick) => (rootNode: T) => { - const visit = (node: ts.Node): ts.Node => { - if (!ts.isSourceFile(node)) { - return node; - } - return context.factory.updateSourceFile( - node, - [ts.factory.createExpressionStatement(expression)], - node.isDeclarationFile, - node.referencedFiles, - node.typeReferenceDirectives, - node.hasNoDefaultLib, - node.libReferenceDirectives, - ); +const traverse = + (expression: ts.Expression) => + (context: Pick) => + (rootNode: T) => { + const visit = (node: ts.Node): ts.Node => { + if (!ts.isSourceFile(node)) { + return node; + } + return context.factory.updateSourceFile( + node, + [ts.factory.createExpressionStatement(expression)], + node.isDeclarationFile, + node.referencedFiles, + node.typeReferenceDirectives, + node.hasNoDefaultLib, + node.libReferenceDirectives, + ); + }; + return ts.visitNode(rootNode, visit); }; - return ts.visitNode(rootNode, visit); -}; const getText = (expression: ts.Expression) => { const source = ts.createSourceFile("", "", ts.ScriptTarget.ESNext); diff --git a/src/code-templates/class-api-client/ApiClientClass/MethodBody/index.ts b/src/code-templates/_shared/MethodBody/index.ts similarity index 86% rename from src/code-templates/class-api-client/ApiClientClass/MethodBody/index.ts rename to src/code-templates/_shared/MethodBody/index.ts index 31dcc796..164e3f98 100644 --- a/src/code-templates/class-api-client/ApiClientClass/MethodBody/index.ts +++ b/src/code-templates/_shared/MethodBody/index.ts @@ -1,26 +1,27 @@ import ts from "typescript"; -import type { TsGenerator } from "../../../../api"; -import type { CodeGenerator } from "../../../../types"; -import { escapeText2 as escapeText } from "../../../../utils"; -import * as Utils from "../../utils"; +import type { TsGenerator } from "../../../api"; +import type { CodeGenerator } from "../../../types"; +import { escapeText2 as escapeText } from "../../../utils"; +import * as Utils from "../../class-api-client/utils"; import * as CallRequest from "./CallRequest"; import * as HeaderParameter from "./HeaderParameter"; import * as PathParameter from "./PathParameter"; import * as QueryParameter from "./QueryParameter"; +import type { MethodType } from "./types"; export interface Params$GenerateUrl { urlTemplate: Utils.Params$TemplateExpression; } -export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator.Params): ts.Statement[] => { +export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator.Params, methodType: MethodType): ts.Statement[] => { const statements: ts.Statement[] = []; const { convertedParams } = params; const { pickedParameters } = convertedParams; // Generate Path Parameter const pathParameters = pickedParameters.filter(PathParameter.isPathParameter); - statements.push(PathParameter.create(factory, params.operationParams.requestUri, pathParameters)); + statements.push(PathParameter.create(factory, params.operationParams.requestUri, pathParameters, methodType)); const initialHeaderObject: Utils.LiteralExpressionObject = {}; if (convertedParams.has2OrMoreRequestContentTypes) { @@ -75,7 +76,7 @@ export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator. // Generate CallRequest statements.push( factory.ReturnStatement.create({ - expression: CallRequest.create(factory, params), + expression: CallRequest.create(factory, params, methodType), }), ); diff --git a/src/code-templates/_shared/MethodBody/types.ts b/src/code-templates/_shared/MethodBody/types.ts new file mode 100644 index 00000000..01e48ef4 --- /dev/null +++ b/src/code-templates/_shared/MethodBody/types.ts @@ -0,0 +1 @@ +export type MethodType = "class" | "function"; diff --git a/src/code-templates/class-api-client/types.ts b/src/code-templates/_shared/types.ts similarity index 100% rename from src/code-templates/class-api-client/types.ts rename to src/code-templates/_shared/types.ts diff --git a/src/code-templates/class-api-client/ApiClientClass/Method.ts b/src/code-templates/class-api-client/ApiClientClass/Method.ts index 75d033a4..11325eb2 100644 --- a/src/code-templates/class-api-client/ApiClientClass/Method.ts +++ b/src/code-templates/class-api-client/ApiClientClass/Method.ts @@ -4,8 +4,8 @@ import ts from "typescript"; import type { TsGenerator } from "../../../api"; import type { CodeGenerator } from "../../../types"; -import type { Option } from "../types"; -import * as MethodBody from "./MethodBody"; +import type { Option } from "../../_shared/types"; +import * as MethodBody from "../../_shared/MethodBody"; export { MethodBody }; @@ -169,7 +169,7 @@ export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator. type: returnType, typeParameters: typeParameters, body: factory.Block.create({ - statements: MethodBody.create(factory, params), + statements: MethodBody.create(factory, params, "class"), multiLine: true, }), }); diff --git a/src/code-templates/class-api-client/ApiClientClass/index.ts b/src/code-templates/class-api-client/ApiClientClass/index.ts index a0d9afb0..a0530baf 100644 --- a/src/code-templates/class-api-client/ApiClientClass/index.ts +++ b/src/code-templates/class-api-client/ApiClientClass/index.ts @@ -2,8 +2,8 @@ import ts from "typescript"; import type { TsGenerator } from "../../../api"; import type { CodeGenerator } from "../../../types"; -import type { Option } from "../types"; -import * as ApiClientInterface from "./ApiClientInterface"; +import type { Option } from "../../_shared/types"; +import * as ApiClientInterface from "../../_shared/ApiClientInterface"; import * as Class from "./Class"; import * as Constructor from "./Constructor"; import * as Method from "./Method"; diff --git a/src/code-templates/class-api-client/index.ts b/src/code-templates/class-api-client/index.ts index 9aee31f9..8c17cd68 100644 --- a/src/code-templates/class-api-client/index.ts +++ b/src/code-templates/class-api-client/index.ts @@ -2,9 +2,9 @@ import ts from "typescript"; import { TsGenerator } from "../../api"; import type { CodeGenerator } from "../../types"; -import * as ApiClientArgument from "./ApiClientArgument"; +import * as ApiClientArgument from "../_shared/ApiClientArgument"; import * as ApiClientClass from "./ApiClientClass"; -import type { Option } from "./types"; +import type { Option } from "../_shared/types"; export { Option }; diff --git a/src/code-templates/functional-api-client/FunctionalApiClient/Method.ts b/src/code-templates/functional-api-client/FunctionalApiClient/Method.ts new file mode 100644 index 00000000..ace52ef1 --- /dev/null +++ b/src/code-templates/functional-api-client/FunctionalApiClient/Method.ts @@ -0,0 +1,179 @@ +import ts from "typescript"; + +import type { TsGenerator } from "../../../api"; +import type { CodeGenerator } from "../../../types"; +import type { Option } from "../../_shared/types"; +import * as MethodBody from "../../_shared/MethodBody"; + +export { MethodBody }; + +const generateParams = (factory: TsGenerator.Factory.Type, { convertedParams }: CodeGenerator.Params) => { + const typeArguments: ts.TypeNode[] = []; + if (convertedParams.has2OrMoreRequestContentTypes) { + typeArguments.push( + factory.TypeReferenceNode.create({ + name: "RequestContentType", + }), + ); + } + if (convertedParams.has2OrMoreSuccessResponseContentTypes) { + typeArguments.push( + factory.TypeReferenceNode.create({ + name: "ResponseContentType", + }), + ); + } + return factory.ParameterDeclaration.create({ + name: "params", + modifiers: undefined, + type: factory.TypeReferenceNode.create({ + name: convertedParams.argumentParamsTypeDeclaration, + typeArguments, + }), + }); +}; + +const generateResponseReturnType = ( + factory: TsGenerator.Factory.Type, + successResponseNameList: string[], + successResponseContentTypeList: string[], + option: Option, +) => { + let objectType: ts.TypeNode = factory.TypeNode.create({ + type: "void", + }); + if (successResponseNameList.length === 1) { + objectType = factory.TypeReferenceNode.create({ + name: successResponseNameList[0], + }); + } else if (successResponseNameList.length > 1) { + objectType = factory.UnionTypeNode.create({ + typeNodes: successResponseNameList.map(item => factory.TypeReferenceNode.create({ name: item })), + }); + } + + // レスポンスが存在しないので Promise + if (successResponseNameList.length === 0) { + if (option.sync) { + return objectType; + } + return factory.TypeReferenceNode.create({ + name: "Promise", + typeArguments: [objectType], + }); + } + + const isOnlyOneResponseContentType = successResponseContentTypeList.length === 1; + let indexType: ts.TypeNode = factory.TypeReferenceNode.create({ + name: "ResponseContentType", + }); + if (isOnlyOneResponseContentType) { + indexType = factory.TypeReferenceNode.create({ + name: `"${successResponseContentTypeList[0]}"`, + }); + } + + if (option.sync) { + return factory.IndexedAccessTypeNode.create({ + objectType, + indexType, + }); + } + + return factory.TypeReferenceNode.create({ + name: "Promise", + typeArguments: [ + factory.IndexedAccessTypeNode.create({ + objectType, + indexType, + }), + ], + }); +}; + +const methodTypeParameters = (factory: TsGenerator.Factory.Type, { convertedParams }: CodeGenerator.Params): ts.TypeParameterDeclaration[] => { + const typeParameters: ts.TypeParameterDeclaration[] = []; + if (convertedParams.has2OrMoreRequestContentTypes) { + typeParameters.push( + factory.TypeParameterDeclaration.create({ + name: "RequestContentType", + constraint: factory.TypeReferenceNode.create({ + name: convertedParams.requestContentTypeName, + }), + }), + ); + } + if (convertedParams.has2OrMoreSuccessResponseContentTypes) { + typeParameters.push( + factory.TypeParameterDeclaration.create({ + name: "ResponseContentType", + constraint: factory.TypeReferenceNode.create({ + name: convertedParams.responseContentTypeName, + }), + }), + ); + } + return typeParameters; +}; + +/** + * const {functionName} = async (params: {argumentParamsTypeDeclaration}<{RequestContentType}>): Promise<{requestBodyName}[ResponseContentType]> => { + * + * } + */ +export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator.Params, option: Option): ts.VariableStatement => { + const { convertedParams } = params; + const typeParameters: ts.TypeParameterDeclaration[] = methodTypeParameters(factory, params); + const methodArguments: ts.ParameterDeclaration[] = []; + const hasParamsArguments = + convertedParams.hasParameter || + convertedParams.hasRequestBody || + convertedParams.has2OrMoreSuccessResponseContentTypes || + convertedParams.has2OrMoreRequestContentTypes; + + if (hasParamsArguments) { + methodArguments.push(generateParams(factory, params)); + } + + const returnType: ts.TypeNode = generateResponseReturnType( + factory, + convertedParams.responseSuccessNames, + convertedParams.successResponseContentTypes, + option, + ); + + methodArguments.push( + factory.ParameterDeclaration.create({ + name: "option", + modifiers: undefined, + optional: true, + type: factory.TypeReferenceNode.create({ + name: "RequestOption", + }), + }), + ); + + const arrowFunction = factory.ArrowFunction.create({ + typeParameters: typeParameters, + parameters: methodArguments, + type: returnType, + body: factory.Block.create({ + statements: MethodBody.create(factory, params, "function"), + multiLine: true, + }), + }); + + const variableDeclarationList = factory.VariableDeclarationList.create({ + declarations: [ + factory.VariableDeclaration.create({ + name: convertedParams.functionName, + initializer: arrowFunction, + }), + ], + flag: "const", + }); + + return factory.VariableStatement.create({ + declarationList: variableDeclarationList, + }); +}; diff --git a/src/code-templates/functional-api-client/FunctionalApiClient/ReturnStatement.ts b/src/code-templates/functional-api-client/FunctionalApiClient/ReturnStatement.ts new file mode 100644 index 00000000..49663c83 --- /dev/null +++ b/src/code-templates/functional-api-client/FunctionalApiClient/ReturnStatement.ts @@ -0,0 +1,18 @@ +import ts from "typescript"; + +import type { TsGenerator } from "../../../api"; +import type { CodeGenerator } from "../../../types"; + +export const create = (factory: TsGenerator.Factory.Type, list: CodeGenerator.Params[]): ts.ReturnStatement => { + const properties = list.map(item => { + return factory.ShorthandPropertyAssignment.create({ + name: item.convertedParams.functionName, + }); + }); + + return factory.ReturnStatement.create({ + expression: factory.ObjectLiteralExpression.create({ + properties, + }), + }); +}; diff --git a/src/code-templates/functional-api-client/FunctionalApiClient/index.ts b/src/code-templates/functional-api-client/FunctionalApiClient/index.ts new file mode 100644 index 00000000..8d622aa5 --- /dev/null +++ b/src/code-templates/functional-api-client/FunctionalApiClient/index.ts @@ -0,0 +1,56 @@ +import ts from "typescript"; + +import type { TsGenerator } from "../../../api"; +import type { CodeGenerator } from "../../../types"; +import type { Option } from "../../_shared/types"; +import * as Method from "./Method"; +import * as ReturnStatement from "./ReturnStatement"; +export { Method }; + +export const create = (factory: TsGenerator.Factory.Type, list: CodeGenerator.Params[], option: Option): ts.VariableStatement => { + const variableStatements = list.map(params => { + return Method.create(factory, params, option); + }); + + const arrowFunction = factory.ArrowFunction.create({ + typeParameters: [ + factory.TypeParameterDeclaration.create({ + name: "RequestOption", + }), + ], + parameters: [ + factory.ParameterDeclaration.create({ + name: "apiClient", + type: factory.TypeReferenceNode.create({ + name: "ApiClient", + typeArguments: [ + factory.TypeReferenceNode.create({ + name: "RequestOption", + }), + ], + }), + }), + factory.ParameterDeclaration.create({ + name: "baseUrl", + type: ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + }), + ], + body: factory.Block.create({ + statements: [...variableStatements, ReturnStatement.create(factory, list)], + multiLine: true, + }), + }); + + return factory.VariableStatement.create({ + modifiers: [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + declarationList: factory.VariableDeclarationList.create({ + declarations: [ + factory.VariableDeclaration.create({ + name: "createClient", + initializer: arrowFunction, + }), + ], + flag: "const", + }), + }); +}; diff --git a/src/code-templates/functional-api-client/index.ts b/src/code-templates/functional-api-client/index.ts new file mode 100644 index 00000000..9a69887d --- /dev/null +++ b/src/code-templates/functional-api-client/index.ts @@ -0,0 +1,40 @@ +import ts from "typescript"; + +import { TsGenerator } from "../../api"; +import type { CodeGenerator } from "../../types"; +import * as ApiClientArgument from "../_shared/ApiClientArgument"; +import * as FunctionalApiClient from "./FunctionalApiClient"; +import * as ApiClientInterface from "../_shared/ApiClientInterface"; + +import type { Option } from "../_shared/types"; + +export { Option }; + +export const generator: CodeGenerator.GenerateFunction