Skip to content

feat: update codegen api and types #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ yarn add -D @himenon/openapi-typescript-code-generator
### DEMO

- [DEMO](./example/README.md)
- [DEMO: github/rest-api-client code generate](https://github.com/Himenon/github-rest-api-client/tree/master/source)
- https://github.com/github/rest-api-description

### Basic usage

Expand All @@ -41,7 +43,7 @@ main();

### Create the original API Client template.

We have an entry point in `option.makeApiClient` to generate non-typed code.
We have an entry point in `option.rewriteCodeAfterTypeDeclaration` to generate non-typed code.
The first argument can be TypeScript's `TransformationContext`, and the second argument contains the information of the type definition generated before this.
By using [ts-ast-viewer](https://ts-ast-viewer.com), code extension by AST can facilitate code extension.

Expand All @@ -56,7 +58,7 @@ const main = () => {
const params: CodeGenerator.Params = {
entryPoint: "your/openapi/spec.yml", // support .yml, .yaml, .json
option: {
makeApiClient: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
rewriteCodeAfterTypeDeclaration: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
const factory = context.factory; // https://ts-ast-viewer.com/ is very very very useful !
return []; // generate no api client
},
Expand Down
4 changes: 2 additions & 2 deletions docs/ja/README-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ main();

### オリジナルの API Client テンプレートを作成する

`option.makeApiClient`に型定義以外のコードを生成するためのエントリーポイントを用意しています。
`option.rewriteCodeAfterTypeDeclaration`に型定義以外のコードを生成するためのエントリーポイントを用意しています。
第 1 引数は TypeScript の`TransformationContext`が利用でき、第 2 引数はこれ以前に生成した型定義の情報が含まれます。
[ts-ast-viewer](https://ts-ast-viewer.com)を利用することにより AST によるコード拡張がコード拡張を円滑にでます。

Expand All @@ -54,7 +54,7 @@ const main = () => {
const params: CodeGenerator.Params = {
entryPoint: "your/openapi/spec.yml", // support .yml, .yaml, .json
option: {
makeApiClient: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
rewriteCodeAfterTypeDeclaration: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
const factory = context.factory; // https://ts-ast-viewer.com/ is very very very useful !
return []; // generate no api client
},
Expand Down
7 changes: 4 additions & 3 deletions example/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Generated by @himenon/openapi-typescript-code-generator v0.0.0
// Generated by @himenon/openapi-typescript-code-generator v0.2.0
//
// OpenApi : 3.0.3
//
Expand Down Expand Up @@ -68,15 +68,16 @@ export interface QueryParameter {
export interface QueryParameters {
[key: string]: QueryParameter;
}
export type SuccessResponses = Response$getBooks$Status$200 | Response$searchBooks$Status$200;
export interface ApiClient<RequestOption> {
request: (
request: <T = SuccessResponses>(
httpMethod: HttpMethod,
url: string,
headers: ObjectLike | any,
requestBody: ObjectLike | any,
queryParameters: QueryParameters | undefined,
options?: RequestOption,
) => Promise<any>;
) => Promise<T>;
}
export class Client<RequestOption> {
constructor(private apiClient: ApiClient<RequestOption>, private baseUrl: string) {}
Expand Down
3 changes: 2 additions & 1 deletion src/CodeGenerator/factory/CallExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ts from "typescript";

export interface Params$Create {
expression: ts.Expression;
typeArguments?: readonly ts.TypeNode[] | undefined;
argumentsArray: readonly ts.Expression[];
}

Expand All @@ -10,7 +11,7 @@ export interface Factory {
}

export const create = ({ factory }: ts.TransformationContext): Factory["create"] => (params: Params$Create): ts.CallExpression => {
const node = factory.createCallExpression(params.expression, undefined, params.argumentsArray);
const node = factory.createCallExpression(params.expression, params.typeArguments, params.argumentsArray);
return node;
};

Expand Down
3 changes: 2 additions & 1 deletion src/CodeGenerator/factory/TypeParameterDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import ts from "typescript";
export interface Params$Create {
name: string;
constraint?: ts.TypeNode;
defaultType?: ts.TypeNode;
}

export interface Factory {
create: (params: Params$Create) => ts.TypeParameterDeclaration;
}

export const create = ({ factory }: ts.TransformationContext): Factory["create"] => (params: Params$Create): ts.TypeParameterDeclaration => {
const node = factory.createTypeParameterDeclaration(factory.createIdentifier(params.name), params.constraint, undefined);
const node = factory.createTypeParameterDeclaration(factory.createIdentifier(params.name), params.constraint, params.defaultType);
return node;
};

Expand Down
9 changes: 6 additions & 3 deletions src/Converter/v3/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,17 @@ const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: Co
return params;
};

export type MakeApiClientFunction = (context: ts.TransformationContext, codeGeneratorParamsList: CodeGeneratorParams[]) => ts.Statement[];
export type RewriteCodeAfterTypeDeclaration = (
context: ts.TransformationContext,
codeGeneratorParamsList: CodeGeneratorParams[],
) => ts.Statement[];

export const generateApiClientCode = (
store: Store.Type,
context: ts.TransformationContext,
converterContext: ConverterContext.Types,
makeApiClient: MakeApiClientFunction,
rewriteCodeAfterTypeDeclaration: RewriteCodeAfterTypeDeclaration,
): void => {
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext);
store.addAdditionalStatement(makeApiClient(context, codeGeneratorParamsList));
store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList));
};
7 changes: 5 additions & 2 deletions src/Converter/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export interface Type {
}

export interface Option {
makeApiClient: Generator.MakeApiClientFunction;
/**
* It is possible to rewrite the implementation after the type declaration.
*/
rewriteCodeAfterTypeDeclaration: Generator.RewriteCodeAfterTypeDeclaration;
}

export const create = (entryPoint: string, rootSchema: OpenApi.Document, noReferenceOpenApiSchema: OpenApi.Document, option: Option): Type => {
Expand Down Expand Up @@ -94,7 +97,7 @@ export const create = (entryPoint: string, rootSchema: OpenApi.Document, noRefer
}
if (rootSchema.paths) {
Paths.generateStatements(entryPoint, currentPoint, store, factory, rootSchema.paths, toTypeNodeContext, converterContext);
Generator.generateApiClientCode(store, context, converterContext, option.makeApiClient);
Generator.generateApiClientCode(store, context, converterContext, option.rewriteCodeAfterTypeDeclaration);
}
return store.getRootStatements();
};
Expand Down
42 changes: 39 additions & 3 deletions src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import ts from "typescript";

import { Factory } from "../../CodeGenerator";
import { CodeGeneratorParams } from "../../Converter/v3";

const httpMethodList: string[] = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];

const createSuccessResponseTypeAlias = (typeName: string, factory: Factory.Type, successResponseNames: string[]) => {
if (successResponseNames.length === 0) {
return factory.TypeAliasDeclaration.create({
export: true,
name: typeName,
type: ts.factory.createToken(ts.SyntaxKind.VoidKeyword),
});
}
return factory.TypeAliasDeclaration.create({
export: true,
name: typeName,
type: factory.UnionTypeNode.create({
typeNodes: successResponseNames.map(name => {
return factory.TypeReferenceNode.create({
name,
});
}),
}),
});
};

const createHttpMethod = (factory: Factory.Type) => {
return factory.TypeAliasDeclaration.create({
export: true,
Expand Down Expand Up @@ -61,7 +83,7 @@ const createObjectLikeInterface = (factory: Factory.Type) => {
});
};

export const create = (factory: Factory.Type): ts.Statement[] => {
export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.Statement[] => {
const objectLikeOrAnyType = factory.UnionTypeNode.create({
typeNodes: [
factory.TypeReferenceNode.create({
Expand Down Expand Up @@ -110,12 +132,25 @@ export const create = (factory: Factory.Type): ts.Statement[] => {
}),
});

const successResponseNames = list.map(item => item.responseSuccessNames).flat();

const functionType = factory.FunctionTypeNode.create({
typeParameters: undefined,
typeParameters: [
factory.TypeParameterDeclaration.create({
name: "T",
defaultType: factory.TypeReferenceNode.create({
name: "SuccessResponses",
}),
}),
],
parameters: [httpMethod, url, headers, requestBody, queryParameters, options],
type: factory.TypeReferenceNode.create({
name: "Promise",
typeArguments: [factory.TypeNode.create({ type: "any" })],
typeArguments: [
factory.TypeReferenceNode.create({
name: "T",
}),
],
}),
});

Expand All @@ -129,6 +164,7 @@ export const create = (factory: Factory.Type): ts.Statement[] => {
createHttpMethod(factory),
createObjectLikeInterface(factory),
...createQueryParamsDeclarations(factory),
createSuccessResponseTypeAlias("SuccessResponses", factory, successResponseNames),
factory.InterfaceDeclaration.create({
export: true,
name: "ApiClient",
Expand Down
2 changes: 1 addition & 1 deletion src/DefaultCodeTemplate/ApiClientClass/Method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const generateResponseReturnType = (factory: Factory.Type, successResponseNameLi
});
}

// レスすポンスが存在しないので Promise<void>
// レスポンスが存在しないので Promise<void>
if (successResponseNameList.length === 0) {
return factory.TypeReferenceNode.create({
name: "Promise",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const create = (factory: Factory.Type, params: CodeGeneratorParams): ts.C

return factory.CallExpression.create({
expression: expression,
typeArguments: [],
argumentsArray: argumentsArray,
});
};
2 changes: 1 addition & 1 deletion src/DefaultCodeTemplate/ApiClientClass/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
return Method.create(factory, params);
});
const members = [Constructor.create(factory), ...methodList];
return [...ApiClientInterface.create(factory), Class.create(factory, members)];
return [...ApiClientInterface.create(factory, list), Class.create(factory, members)];
};
2 changes: 1 addition & 1 deletion src/DefaultCodeTemplate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as Converter from "../Converter";
import * as ApiClientArgument from "./ApiClientArgument";
import * as ApiClientClass from "./ApiClientClass";

export const makeClientApiClient: Converter.v3.Generator.MakeApiClientFunction = (
export const rewriteCodeAfterTypeDeclaration: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration = (
context: ts.TransformationContext,
codeGeneratorParamsList: Converter.v3.CodeGeneratorParams[],
): ts.Statement[] => {
Expand Down
11 changes: 5 additions & 6 deletions src/DefaultCodeTemplate/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,11 @@ export const generateObjectLiteralExpression = (
});
};

export const stringToArray = (text: string, delimiter: string): string[] => {
const list = text.split(delimiter);
return list.reduce<string[]>((current, item, index) => {
return index < list.length - 1 ? current.concat([item, delimiter]) : current.concat([item]);
}, []);
};
/**
* "/{a}/b/{a}/c{a}/".split(new RegExp("({a})"))
* => ["/", "{a}", "/b/", "{a}", "/c", "{a}", "/"]
*/
export const stringToArray = (text: string, delimiter: string): string[] => text.split(new RegExp(`(${delimiter})`));

export const multiSplitStringToArray = (text: string, delimiters: string[]): string[] => {
return delimiters.reduce<string[]>(
Expand Down
8 changes: 5 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export { Converter };

export interface Params {
entryPoint: string;
option?: Partial<Converter.v3.Option>;
option?: {
rewriteCodeAfterTypeDeclaration?: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BREAKING

};
/** default: true */
enableValidate?: boolean;
log?: {
Expand All @@ -28,8 +30,8 @@ export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = tr
}

const convertOption: Converter.v3.Option = option
? { makeApiClient: option.makeApiClient || DefaultCodeTemplate.makeClientApiClient }
: { makeApiClient: DefaultCodeTemplate.makeClientApiClient };
? { rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration }
: { rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration };
const { createFunction, generateLeadingComment } = Converter.v3.create(entryPoint, schema, resolvedReferenceDocument, convertOption);
return [generateLeadingComment(), TypeScriptCodeGenerator.generate(createFunction)].join(EOL + EOL + EOL);
};
3 changes: 2 additions & 1 deletion test/__tests__/__snapshots__/snapshot-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,9 @@ export interface QueryParameter {
export interface QueryParameters {
[key: string]: QueryParameter;
}
export type SuccessResponses = Response$getIncludeLocalReference$Status$200 | Response$getFullRemoteReference$Status$200 | Response$getReferenceItems$Status$200;
export interface ApiClient<RequestOption> {
request: (httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise<any>;
request: <T = SuccessResponses>(httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise<T>;
}
export class Client<RequestOption> {
constructor(private apiClient: ApiClient<RequestOption>, private baseUrl: string) { }
Expand Down