Skip to content

Commit 1c6e748

Browse files
authored
feat: update codegen api and types (#17)
BREAKING_CHANGE: rewrite api name
1 parent 71569cc commit 1c6e748

File tree

15 files changed

+80
-30
lines changed

15 files changed

+80
-30
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ yarn add -D @himenon/openapi-typescript-code-generator
2020
### DEMO
2121

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

2426
### Basic usage
2527

@@ -41,7 +43,7 @@ main();
4143

4244
### Create the original API Client template.
4345

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

@@ -56,7 +58,7 @@ const main = () => {
5658
const params: CodeGenerator.Params = {
5759
entryPoint: "your/openapi/spec.yml", // support .yml, .yaml, .json
5860
option: {
59-
makeApiClient: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
61+
rewriteCodeAfterTypeDeclaration: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
6062
const factory = context.factory; // https://ts-ast-viewer.com/ is very very very useful !
6163
return []; // generate no api client
6264
},

docs/ja/README-ja.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ main();
3939

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

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

@@ -54,7 +54,7 @@ const main = () => {
5454
const params: CodeGenerator.Params = {
5555
entryPoint: "your/openapi/spec.yml", // support .yml, .yaml, .json
5656
option: {
57-
makeApiClient: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
57+
rewriteCodeAfterTypeDeclaration: (context: ts.TransformationContext, codeGeneratorParamsList: CodeGenerator.Converter.v3.CodeGeneratorParams[]): ts.Statement[] => {
5858
const factory = context.factory; // https://ts-ast-viewer.com/ is very very very useful !
5959
return []; // generate no api client
6060
},

example/client.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Generated by @himenon/openapi-typescript-code-generator v0.0.0
2+
// Generated by @himenon/openapi-typescript-code-generator v0.2.0
33
//
44
// OpenApi : 3.0.3
55
//
@@ -68,15 +68,16 @@ export interface QueryParameter {
6868
export interface QueryParameters {
6969
[key: string]: QueryParameter;
7070
}
71+
export type SuccessResponses = Response$getBooks$Status$200 | Response$searchBooks$Status$200;
7172
export interface ApiClient<RequestOption> {
72-
request: (
73+
request: <T = SuccessResponses>(
7374
httpMethod: HttpMethod,
7475
url: string,
7576
headers: ObjectLike | any,
7677
requestBody: ObjectLike | any,
7778
queryParameters: QueryParameters | undefined,
7879
options?: RequestOption,
79-
) => Promise<any>;
80+
) => Promise<T>;
8081
}
8182
export class Client<RequestOption> {
8283
constructor(private apiClient: ApiClient<RequestOption>, private baseUrl: string) {}

src/CodeGenerator/factory/CallExpression.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ts from "typescript";
22

33
export interface Params$Create {
44
expression: ts.Expression;
5+
typeArguments?: readonly ts.TypeNode[] | undefined;
56
argumentsArray: readonly ts.Expression[];
67
}
78

@@ -10,7 +11,7 @@ export interface Factory {
1011
}
1112

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

src/CodeGenerator/factory/TypeParameterDeclaration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import ts from "typescript";
33
export interface Params$Create {
44
name: string;
55
constraint?: ts.TypeNode;
6+
defaultType?: ts.TypeNode;
67
}
78

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

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

src/Converter/v3/Generator.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,17 @@ const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: Co
103103
return params;
104104
};
105105

106-
export type MakeApiClientFunction = (context: ts.TransformationContext, codeGeneratorParamsList: CodeGeneratorParams[]) => ts.Statement[];
106+
export type RewriteCodeAfterTypeDeclaration = (
107+
context: ts.TransformationContext,
108+
codeGeneratorParamsList: CodeGeneratorParams[],
109+
) => ts.Statement[];
107110

108111
export const generateApiClientCode = (
109112
store: Store.Type,
110113
context: ts.TransformationContext,
111114
converterContext: ConverterContext.Types,
112-
makeApiClient: MakeApiClientFunction,
115+
rewriteCodeAfterTypeDeclaration: RewriteCodeAfterTypeDeclaration,
113116
): void => {
114117
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext);
115-
store.addAdditionalStatement(makeApiClient(context, codeGeneratorParamsList));
118+
store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList));
116119
};

src/Converter/v3/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ export interface Type {
2424
}
2525

2626
export interface Option {
27-
makeApiClient: Generator.MakeApiClientFunction;
27+
/**
28+
* It is possible to rewrite the implementation after the type declaration.
29+
*/
30+
rewriteCodeAfterTypeDeclaration: Generator.RewriteCodeAfterTypeDeclaration;
2831
}
2932

3033
export const create = (entryPoint: string, rootSchema: OpenApi.Document, noReferenceOpenApiSchema: OpenApi.Document, option: Option): Type => {
@@ -94,7 +97,7 @@ export const create = (entryPoint: string, rootSchema: OpenApi.Document, noRefer
9497
}
9598
if (rootSchema.paths) {
9699
Paths.generateStatements(entryPoint, currentPoint, store, factory, rootSchema.paths, toTypeNodeContext, converterContext);
97-
Generator.generateApiClientCode(store, context, converterContext, option.makeApiClient);
100+
Generator.generateApiClientCode(store, context, converterContext, option.rewriteCodeAfterTypeDeclaration);
98101
}
99102
return store.getRootStatements();
100103
};

src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
11
import ts from "typescript";
22

33
import { Factory } from "../../CodeGenerator";
4+
import { CodeGeneratorParams } from "../../Converter/v3";
45

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

8+
const createSuccessResponseTypeAlias = (typeName: string, factory: Factory.Type, successResponseNames: string[]) => {
9+
if (successResponseNames.length === 0) {
10+
return factory.TypeAliasDeclaration.create({
11+
export: true,
12+
name: typeName,
13+
type: ts.factory.createToken(ts.SyntaxKind.VoidKeyword),
14+
});
15+
}
16+
return factory.TypeAliasDeclaration.create({
17+
export: true,
18+
name: typeName,
19+
type: factory.UnionTypeNode.create({
20+
typeNodes: successResponseNames.map(name => {
21+
return factory.TypeReferenceNode.create({
22+
name,
23+
});
24+
}),
25+
}),
26+
});
27+
};
28+
729
const createHttpMethod = (factory: Factory.Type) => {
830
return factory.TypeAliasDeclaration.create({
931
export: true,
@@ -61,7 +83,7 @@ const createObjectLikeInterface = (factory: Factory.Type) => {
6183
});
6284
};
6385

64-
export const create = (factory: Factory.Type): ts.Statement[] => {
86+
export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.Statement[] => {
6587
const objectLikeOrAnyType = factory.UnionTypeNode.create({
6688
typeNodes: [
6789
factory.TypeReferenceNode.create({
@@ -110,12 +132,25 @@ export const create = (factory: Factory.Type): ts.Statement[] => {
110132
}),
111133
});
112134

135+
const successResponseNames = list.map(item => item.responseSuccessNames).flat();
136+
113137
const functionType = factory.FunctionTypeNode.create({
114-
typeParameters: undefined,
138+
typeParameters: [
139+
factory.TypeParameterDeclaration.create({
140+
name: "T",
141+
defaultType: factory.TypeReferenceNode.create({
142+
name: "SuccessResponses",
143+
}),
144+
}),
145+
],
115146
parameters: [httpMethod, url, headers, requestBody, queryParameters, options],
116147
type: factory.TypeReferenceNode.create({
117148
name: "Promise",
118-
typeArguments: [factory.TypeNode.create({ type: "any" })],
149+
typeArguments: [
150+
factory.TypeReferenceNode.create({
151+
name: "T",
152+
}),
153+
],
119154
}),
120155
});
121156

@@ -129,6 +164,7 @@ export const create = (factory: Factory.Type): ts.Statement[] => {
129164
createHttpMethod(factory),
130165
createObjectLikeInterface(factory),
131166
...createQueryParamsDeclarations(factory),
167+
createSuccessResponseTypeAlias("SuccessResponses", factory, successResponseNames),
132168
factory.InterfaceDeclaration.create({
133169
export: true,
134170
name: "ApiClient",

src/DefaultCodeTemplate/ApiClientClass/Method.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const generateResponseReturnType = (factory: Factory.Type, successResponseNameLi
4646
});
4747
}
4848

49-
// レスすポンスが存在しないので Promise<void>
49+
// レスポンスが存在しないので Promise<void>
5050
if (successResponseNameList.length === 0) {
5151
return factory.TypeReferenceNode.create({
5252
name: "Promise",

src/DefaultCodeTemplate/ApiClientClass/MethodBody/CallRequest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const create = (factory: Factory.Type, params: CodeGeneratorParams): ts.C
2525

2626
return factory.CallExpression.create({
2727
expression: expression,
28+
typeArguments: [],
2829
argumentsArray: argumentsArray,
2930
});
3031
};

src/DefaultCodeTemplate/ApiClientClass/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
1414
return Method.create(factory, params);
1515
});
1616
const members = [Constructor.create(factory), ...methodList];
17-
return [...ApiClientInterface.create(factory), Class.create(factory, members)];
17+
return [...ApiClientInterface.create(factory, list), Class.create(factory, members)];
1818
};

src/DefaultCodeTemplate/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as Converter from "../Converter";
55
import * as ApiClientArgument from "./ApiClientArgument";
66
import * as ApiClientClass from "./ApiClientClass";
77

8-
export const makeClientApiClient: Converter.v3.Generator.MakeApiClientFunction = (
8+
export const rewriteCodeAfterTypeDeclaration: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration = (
99
context: ts.TransformationContext,
1010
codeGeneratorParamsList: Converter.v3.CodeGeneratorParams[],
1111
): ts.Statement[] => {

src/DefaultCodeTemplate/utils.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,11 @@ export const generateObjectLiteralExpression = (
198198
});
199199
};
200200

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

208207
export const multiSplitStringToArray = (text: string, delimiters: string[]): string[] => {
209208
return delimiters.reduce<string[]>(

src/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export { Converter };
1111

1212
export interface Params {
1313
entryPoint: string;
14-
option?: Partial<Converter.v3.Option>;
14+
option?: {
15+
rewriteCodeAfterTypeDeclaration?: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration;
16+
};
1517
/** default: true */
1618
enableValidate?: boolean;
1719
log?: {
@@ -28,8 +30,8 @@ export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = tr
2830
}
2931

3032
const convertOption: Converter.v3.Option = option
31-
? { makeApiClient: option.makeApiClient || DefaultCodeTemplate.makeClientApiClient }
32-
: { makeApiClient: DefaultCodeTemplate.makeClientApiClient };
33+
? { rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration }
34+
: { rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration };
3335
const { createFunction, generateLeadingComment } = Converter.v3.create(entryPoint, schema, resolvedReferenceDocument, convertOption);
3436
return [generateLeadingComment(), TypeScriptCodeGenerator.generate(createFunction)].join(EOL + EOL + EOL);
3537
};

test/__tests__/__snapshots__/snapshot-test.ts.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,9 @@ export interface QueryParameter {
365365
export interface QueryParameters {
366366
[key: string]: QueryParameter;
367367
}
368+
export type SuccessResponses = Response$getIncludeLocalReference$Status$200 | Response$getFullRemoteReference$Status$200 | Response$getReferenceItems$Status$200;
368369
export interface ApiClient<RequestOption> {
369-
request: (httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise<any>;
370+
request: <T = SuccessResponses>(httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise<T>;
370371
}
371372
export class Client<RequestOption> {
372373
constructor(private apiClient: ApiClient<RequestOption>, private baseUrl: string) { }

0 commit comments

Comments
 (0)