このライブラリは OpenAPI v3.0.x 系に準拠した仕様書から TypeScript の型定義と抽出したパラメーターを提供します。 コードの生成には TypeScript AST を利用し、正確に TypeScript のコードへ変換します。 OpenAPI から抽出したパラメーターは自由に使うことができるため、API Client や Server Side 用のコード、ロードバランサーの設定ファイルなどの自動生成に役立てることができます。
yarn add -D @himenon/openapi-typescript-code-generator
ここで記されている例はDEMO 用のリポジトリをクローンして動作確認することができます。
import * as fs from "fs";
import { CodeGenerator } from "@himenon/openapi-typescript-code-generator";
const main = () => {
const codeGenerator = new CodeGenerator("your/openapi/spec.yml");
const code = codeGenerator.generateTypeDefinition();
fs.writeFileSync("client.ts", code, { encoding: "utf-8" });
};
main();
import * as fs from "fs";
import { CodeGenerator } from "@himenon/openapi-typescript-code-generator";
import * as Templates from "@himenon/openapi-typescript-code-generator/dist/templates";
import type * as Types from "@himenon/openapi-typescript-code-generator/dist/types";
const main = () => {
const codeGenerator = new CodeGenerator("your/openapi/spec.yml");
const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator<Templates.FunctionalApiClient.Option> = {
generator: Templates.FunctionalApiClient.generator,
option: {},
};
const code = codeGenerator.generateTypeDefinition([
codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(),
apiClientGeneratorTemplate,
]);
fs.writeFileSync("client.ts", code, { encoding: "utf-8" });
};
main();
本ライブラリからは 3 種類提供しています。
import * as Templates from "@himenon/openapi-typescript-code-generator/dist/templates";
Templates.ClassApiClient.generator;
Templates.FunctionalApiClient.generator;
Templates.CurryingFunctionalApiClient.generator;
class ベースの API Client を提供しています。constructor
より API Client の依存を注入して利用してください。
export interface RequestArgs {
httpMethod: HttpMethod;
url: string;
headers: ObjectLike | any;
requestBody?: ObjectLike | any;
requestBodyEncoding?: Record<string, Encoding>;
queryParameters?: QueryParameters | undefined;
}
export interface ApiClient<RequestOption> {
request: <T = SuccessResponses>(requestArgs: RequestArgs, options?: RequestOption) => Promise<T>;
}
export class Client<RequestOption> {
private baseUrl: string;
constructor(
private apiClient: ApiClient<RequestOption>,
baseUrl: string,
) {
this.baseUrl = baseUrl.replace(/\/$/, "");
}
public async createPublisherV2<RequestContentType extends RequestContentType$createPublisherV2>(
params: Params$createPublisherV2<RequestContentType>,
option?: RequestOption,
): Promise<Response$createPublisherV2$Status$200["application/json"]> {
const url = this.baseUrl + `/create/v2/publisher/{id}`;
const headers = {
"Content-Type": params.headers["Content-Type"],
Accept: "application/json",
};
const requestEncodings = {
"application/x-www-form-urlencoded": {
color: {
style: "form",
explode: false,
},
},
"application/json": {
color: {
style: "form",
explode: false,
},
},
};
return this.apiClient.request(
{
httpMethod: "POST",
url,
headers,
requestBody: params.requestBody,
requestBodyEncoding: requestEncodings[params.headers["Content-Type"]],
},
option,
);
}
}
関数 ベースの API Client を提供しています。createClient
より API Client の依存を注入して利用してください。
class ベースの API Client をそのまま関数ベースに置き換えたものです。
export interface RequestArgs {
httpMethod: HttpMethod;
url: string;
headers: ObjectLike | any;
requestBody?: ObjectLike | any;
requestBodyEncoding?: Record<string, Encoding>;
queryParameters?: QueryParameters | undefined;
}
export interface ApiClient<RequestOption> {
request: <T = SuccessResponses>(requestArgs: RequestArgs, options?: RequestOption) => Promise<T>;
}
export const createClient = <RequestOption>(apiClient: ApiClient<RequestOption>, baseUrl: string) => {
const _baseUrl = baseUrl.replace(/\/$/, "");
return {
createPublisherV2: <RequestContentType extends RequestContentType$createPublisherV2>(
params: Params$createPublisherV2<RequestContentType>,
option?: RequestOption,
): Promise<Response$createPublisherV2$Status$200["application/json"]> => {
const url = _baseUrl + `/create/v2/publisher/{id}`;
const headers = {
"Content-Type": params.headers["Content-Type"],
Accept: "application/json",
};
const requestEncodings = {
"application/x-www-form-urlencoded": {
color: {
style: "form",
explode: false,
},
},
"application/json": {
color: {
style: "form",
explode: false,
},
},
};
return apiClient.request(
{
httpMethod: "POST",
url,
headers,
requestBody: params.requestBody,
requestBodyEncoding: requestEncodings[params.headers["Content-Type"]],
},
option,
);
},
};
};
Tree Shaking 対応
カリー化された関数ベースの API Client を提供しています。各operationId
毎に API Client を注入する形式を取っています。
第 1 の関数引数にはApiClient
を要求し、第 2 の関数の引数にRequestArgs
を要求します。ApiClient
の Interface は他と異なり、uri
を引数として要求します。
Tree Shaking を利用するようなユースーケースで利用することを想定しています。
export interface RequestArgs {
httpMethod: HttpMethod;
uri: string; // <------------------ uriであることに注意
headers: ObjectLike | any;
requestBody?: ObjectLike | any;
requestBodyEncoding?: Record<string, Encoding>;
queryParameters?: QueryParameters | undefined;
}
export interface ApiClient<RequestOption> {
request: <T = SuccessResponses>(requestArgs: RequestArgs, options?: RequestOption) => Promise<T>;
}
export const createPublisherV2 =
<RequestOption>(apiClient: ApiClient<RequestOption>) =>
<RequestContentType extends RequestContentType$createPublisherV2>(
params: Params$createPublisherV2<RequestContentType>,
option?: RequestOption,
): Promise<Response$createPublisherV2$Status$200["application/json"]> => {
const uri = `/create/v2/publisher/{id}`;
const headers = {
"Content-Type": params.headers["Content-Type"],
Accept: "application/json",
};
const requestEncodings = {
"application/x-www-form-urlencoded": {
color: {
style: "form",
explode: false,
},
},
"application/json": {
color: {
style: "form",
explode: false,
},
},
};
return apiClient.request(
{
httpMethod: "POST",
uri,
headers,
requestBody: params.requestBody,
requestBodyEncoding: requestEncodings[params.headers["Content-Type"]],
},
option,
);
};
import * as fs from "fs";
import { CodeGenerator } from "@himenon/openapi-typescript-code-generator";
import * as Templates from "@himenon/openapi-typescript-code-generator/dist/templates";
import type * as Types from "@himenon/openapi-typescript-code-generator/dist/types";
const main = () => {
const codeGenerator = new CodeGenerator("your/openapi/spec.yml");
const apiClientGeneratorTemplate: Types.CodeGenerator.CustomGenerator<Templates.FunctionalApiClient.Option> = {
generator: Templates.FunctionalApiClient.generator,
option: {},
};
const typeDefCode = codeGenerator.generateTypeDefinition();
const apiClientCode = codeGenerator.generateCode([
{
generator: () => {
return [`import { Schemas, Responses } from "./types";`];
},
},
codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(),
apiClientGeneratorTemplate,
]);
fs.writeFileSync("types.ts", typeDefCode, { encoding: "utf-8" });
fs.writeFileSync("apiClient.ts", apiClientCode, { encoding: "utf-8" });
};
main();
この節で示す例は以下に示す方法で利用できます
import * as fs from "fs";
import { CodeGenerator } from "@himenon/openapi-typescript-code-generator";
import type * as Types from "@himenon/openapi-typescript-code-generator/dist/types";
/** ここにCode Templateの定義を記述してください */
const customGenerator: Types.CodeGenerator.CustomGenerator<{}> = {
/** .... */
};
const codeGenerator = new CodeGenerator("your/openapi/spec.yml");
const code = codeGenerator.generateCode([customGenerator]);
fs.writeFileSync("output/file/name", code, { encoding: "utf-8" });
独自定義のコードジェネレーターはstring
の配列を返すことができます。
import * as Types from "@himenon/openapi-typescript-code-generator/dist/types";
interface Option {
showLog?: boolean;
}
const generator: Types.CodeGenerator.GenerateFunction<Option> = (payload: Types.CodeGenerator.Params[], option): string[] => {
if (option && option.showLog) {
console.log("show log message");
}
return ["Hello world"];
};
const customGenerator: Types.CodeGenerator.CustomGenerator<Option> = {
generator: generator,
option: {},
};
独自定義のコードジェネレーターは、OpenAPI Schema から抽出したパラメーターを受け取ることができます。 利用可能なパラメーターは型定義を参照してください。
import * as Types from "@himenon/openapi-typescript-code-generator/dist/types";
interface Option {}
const generator: Types.CodeGenerator.GenerateFunction<Option> = (payload: Types.CodeGenerator.Params[], option): string[] => {
return payload.map(params => {
return `function ${params.operationId}() { console.log("${params.comment}") }`;
});
};
const customGenerator: Types.CodeGenerator.CustomGenerator<Option> = {
generator: generator,
option: {},
};
以下のようなformat
が指定された Data Type を任意の型定義に変換します。
components:
schemas:
Binary:
type: string
format: binary
IntOrString:
type: string
format: int-or-string
AandB:
type: string
format: A-and-B
Data Type Format を任意の型定義に変換するオプションは次のように定義します。
import { CodeGenerator, Option } from "@himenon/openapi-typescript-code-generator";
const option: Option = {
convertOption: {
formatConversions: [
{
selector: {
format: "binary",
},
output: {
type: ["Blob"],
},
},
{
selector: {
format: "int-or-string",
},
output: {
type: ["number", "string"],
},
},
{
selector: {
format: "A-and-B",
},
output: {
type: ["A", "B"],
multiType: "allOf",
},
},
],
},
};
const codeGenerator = new CodeGenerator(inputFilename, option);
これで生成される型定義は次のようになります。
export namespace Schemas {
export type Binary = Blob;
export type IntOrString = number | string;
export type AandB = A & B;
}
TypeScript AST の API を利用したコードの拡張が可能です。 直接 TypeScript の AST の API を利用したり、本ライブラリが提供する TypeScript AST のラッパー API を利用できます。
import * as Types from "@himenon/openapi-typescript-code-generator/dist/types";
import { TsGenerator } from "@himenon/openapi-typescript-code-generator/dist/api";
interface Option {}
const factory = TsGenerator.Factory.create();
const generator: Types.CodeGenerator.GenerateFunction<Option> = (
payload: Types.CodeGenerator.Params[],
option,
): Types.CodeGenerator.IntermediateCode[] => {
return payload.map(params => {
return factory.InterfaceDeclaration.create({
export: true,
name: params.functionName,
members: [],
});
});
};
const customGenerator: Types.CodeGenerator.CustomGenerator<Option> = {
generator: generator,
option: {},
};
import { CodeGenerator } from "@himenon/openapi-typescript-code-generator";
入力された OpenAPI Schema のバリデーションを実行します。
OpenAPI Schema を TypeScript の型定義に変換したコードを生成します。
独自のコードジェネレーターを複数指定することができ、ジェネレーターは OpenAPI Schema から抽出したパラメーターを利用できます。
内部でstring
またはts.Statement
の配列を文字列として変換を行います。
たとえばファイルの分割の単位でジェネレーターを作成するとジェネレーターの再利用性が高まります。
OpenAPI Schema から抽出したパラメーターを取得できます。
Templates.FunctionalApiClient
向けの型定義ファイルです。generateTypeDefinition
に含めていない理由は、用途によってこの関数が生成する型定義を利用しない可能性があるためです。
※ 将来的にTemplates
の API に移動する予定です。
import { TsGenerator } from "@himenon/openapi-typescript-code-generator/dist/api";
内部で利用している TypeScript AST のラッパー API です。 告知なく変更する可能性があります。
import { OpenApiTools } from "@himenon/openapi-typescript-code-generator/dist/api";
OpenApiTools.Parser
OpenAPI Schema をパースするための API です。 告知なく変更する可能性があります。
サポートしているディレクトリ構造に制限があります。
ディレクトリ構造を TypeScript の Namespace へ変換するとき、実装を簡素化するために$ref
を利用した Remote Reference は以下のディレクトリ構造にのみ定義してください。
もし拡張したい場合は本リポジトリを Fork して独自に行ってください。
spec.yml // entry file
components/
headers/
parameters/
pathItems/
requestBodies/
responses/
schemas/
paths/
$ref: http://....
は現在サポートしていません。将来的にサポートしたいと考えています。
はじめに、興味を持っていただきありがとうございます。 API 仕様書から TypeScript のコードへ変換するとき、参照関係を解決することは特に大変で、テストケースが十分でない可能性があります。 テストケースを追加することは、挙動を安定化させるために非常に強力な支えになるので、挙動がおかしな不具合を見つけたらぜひ報告してください。 また、本リポジトリの基本的な設計コンセプトは以下にあるとおりです。これらに沿わない変更を行いたい場合はフォークして拡張してください。 設計コンセプトに沿う変更内容でしたらぜひ Pull Request か Issue を投稿してください!
- 型定義ファーストであること
- 型定義に実体が含まれないこと(型定義部分を
.js
に変換したとき、ファイルサイズが 0 になること) - ディレクトリ構造が型定義の構造に写像されること
- どの API クライアントライブラリにも依存しないこと
- TypeScript AST による拡張ができること
- OpenAPI の仕様に準拠すること
- 1 ファイル化することにより、ポータビリティを保つこと
git clone https://github.com/Himenon/openapi-typescript-code-generator.git
cd openapi-typescript-code-generator
pnpm i
#### your change
pnpm build
pnpm run test:code:gen
pnpm run update:snapshot # if you changed
pnpm run test
TypeScript AST
@himenon/openapi-typescript-code-generator・MIT
Validation 設計
- Copyright (c) 2018 Kogo Software LLC - https://github.com/kogosoftwarellc/open-api/tree/master/packages/openapi-schema-validator#readme