Skip to content

Commit 4bbde34

Browse files
authored
feat: Support sync API Client generation (#22)
* feat: Added option to output API Client in sync mode
1 parent 103dde6 commit 4bbde34

File tree

10 files changed

+520
-38
lines changed

10 files changed

+520
-38
lines changed

scripts/testCodeGen.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,31 @@ const gen = (name: string, enableValidate = true): void => {
1818
console.log(`Generate Code : test/code/${name}.ts`);
1919
};
2020

21+
const genSyncMode = (name: string, enableValidate = true): void => {
22+
const params: CodeGenerator.Params = {
23+
entryPoint: `test/${name}/index.yml`,
24+
enableValidate,
25+
option: {
26+
codeGenerator: {
27+
sync: true,
28+
},
29+
},
30+
log: {
31+
validator: {
32+
displayLogLines: 1,
33+
},
34+
},
35+
};
36+
fs.mkdirSync("test/code", { recursive: true });
37+
const code = CodeGenerator.generateTypeScriptCode(params);
38+
fs.writeFileSync(`test/code/sync-${name}.ts`, code, { encoding: "utf-8" });
39+
console.log(`Generate Code : test/code/sync-${name}.ts`);
40+
};
41+
2142
const main = () => {
2243
gen("api.test.domain");
2344
gen("infer.domain", false);
45+
genSyncMode("api.test.domain");
2446
};
2547

2648
main();

src/Converter/v3/Generator.ts renamed to src/Converter/v3/CodeGenerator.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,14 @@ const generateCodeGeneratorParamsList = (
121121
return params;
122122
};
123123

124+
export interface Option {
125+
sync: boolean;
126+
}
127+
124128
export type RewriteCodeAfterTypeDeclaration = (
125129
context: ts.TransformationContext,
126130
codeGeneratorParamsList: CodeGeneratorParams[],
131+
codeGenerateOption: Option,
127132
) => ts.Statement[];
128133

129134
export const generateApiClientCode = (
@@ -132,7 +137,8 @@ export const generateApiClientCode = (
132137
converterContext: ConverterContext.Types,
133138
rewriteCodeAfterTypeDeclaration: RewriteCodeAfterTypeDeclaration,
134139
allowOperationIds: string[] | undefined,
140+
option: Option,
135141
): void => {
136142
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext, allowOperationIds);
137-
store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList));
143+
store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList, option));
138144
};

src/Converter/v3/index.ts

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

33
import * as TypeScriptCodeGenerator from "../../CodeGenerator";
4+
import * as CodeGenerator from "./CodeGenerator";
45
import * as Comment from "./Comment";
56
import * as Headers from "./components/Headers";
67
import * as Parameters from "./components/Parameters";
@@ -9,26 +10,32 @@ import * as RequestBodies from "./components/RequestBodies";
910
import * as Responses from "./components/Responses";
1011
import * as Schemas from "./components/Schemas";
1112
import * as ConvertContext from "./ConverterContext";
12-
import * as Generator from "./Generator";
1313
import * as Name from "./Name";
1414
import * as Paths from "./paths";
1515
import { Store } from "./store";
1616
import * as TypeNodeContext from "./TypeNodeContext";
1717
import { CodeGeneratorParams, OpenApi, PickedParameter } from "./types";
1818

19-
export { OpenApi, Generator, CodeGeneratorParams, PickedParameter, Name };
19+
export { OpenApi, CodeGenerator, CodeGeneratorParams, PickedParameter, Name };
2020

2121
export interface Type {
2222
generateLeadingComment: () => string;
2323
createFunction: TypeScriptCodeGenerator.CreateFunction;
24+
codeGeneratorOption: CodeGenerator.Option;
2425
}
2526

2627
export interface Option {
2728
/**
2829
* It is possible to rewrite the implementation after the type declaration.
2930
*/
30-
rewriteCodeAfterTypeDeclaration: Generator.RewriteCodeAfterTypeDeclaration;
31-
31+
rewriteCodeAfterTypeDeclaration: CodeGenerator.RewriteCodeAfterTypeDeclaration;
32+
/**
33+
*
34+
*/
35+
codeGeneratorOption: CodeGenerator.Option;
36+
/**
37+
* List of operationId to be used
38+
*/
3239
allowOperationIds?: string[];
3340
}
3441

@@ -99,13 +106,21 @@ export const create = (entryPoint: string, rootSchema: OpenApi.Document, noRefer
99106
}
100107
if (rootSchema.paths) {
101108
Paths.generateStatements(entryPoint, currentPoint, store, factory, rootSchema.paths, toTypeNodeContext, converterContext);
102-
Generator.generateApiClientCode(store, context, converterContext, option.rewriteCodeAfterTypeDeclaration, option.allowOperationIds);
109+
CodeGenerator.generateApiClientCode(
110+
store,
111+
context,
112+
converterContext,
113+
option.rewriteCodeAfterTypeDeclaration,
114+
option.allowOperationIds,
115+
option.codeGeneratorOption,
116+
);
103117
}
104118
return store.getRootStatements();
105119
};
106120

107121
return {
108122
createFunction,
109123
generateLeadingComment: () => Comment.generateLeading(rootSchema),
124+
codeGeneratorOption: option.codeGeneratorOption,
110125
};
111126
};

src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import ts from "typescript";
22

33
import { Factory } from "../../CodeGenerator";
4-
import { CodeGeneratorParams } from "../../Converter/v3";
4+
import type { CodeGenerator, CodeGeneratorParams } from "../../Converter/v3";
55

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

@@ -104,7 +104,7 @@ const createObjectLikeInterface = (factory: Factory.Type) => {
104104
});
105105
};
106106

107-
export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.Statement[] => {
107+
export const create = (factory: Factory.Type, list: CodeGeneratorParams[], option: CodeGenerator.Option): ts.Statement[] => {
108108
const objectLikeOrAnyType = factory.UnionTypeNode.create({
109109
typeNodes: [
110110
factory.TypeReferenceNode.create({
@@ -163,6 +163,19 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
163163
}),
164164
});
165165

166+
const returnType = option.sync
167+
? factory.TypeReferenceNode.create({
168+
name: "T",
169+
})
170+
: factory.TypeReferenceNode.create({
171+
name: "Promise",
172+
typeArguments: [
173+
factory.TypeReferenceNode.create({
174+
name: "T",
175+
}),
176+
],
177+
});
178+
166179
const functionType = factory.FunctionTypeNode.create({
167180
typeParameters: [
168181
factory.TypeParameterDeclaration.create({
@@ -173,14 +186,7 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
173186
}),
174187
],
175188
parameters: [httpMethod, url, headers, requestBody, queryParameters, options],
176-
type: factory.TypeReferenceNode.create({
177-
name: "Promise",
178-
typeArguments: [
179-
factory.TypeReferenceNode.create({
180-
name: "T",
181-
}),
182-
],
183-
}),
189+
type: returnType,
184190
});
185191

186192
const requestFunction = factory.PropertySignature.create({

src/DefaultCodeTemplate/ApiClientClass/Method.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import ts from "typescript";
22

33
import { Factory } from "../../CodeGenerator";
4-
import { CodeGeneratorParams } from "../../Converter/v3";
4+
import type { CodeGenerator, CodeGeneratorParams } from "../../Converter/v3";
55
import * as MethodBody from "./MethodBody";
66

77
export { MethodBody };
@@ -32,7 +32,12 @@ const generateParams = (factory: Factory.Type, params: CodeGeneratorParams) => {
3232
});
3333
};
3434

35-
const generateResponseReturnType = (factory: Factory.Type, successResponseNameList: string[], successResponseContentTypeList: string[]) => {
35+
const generateResponseReturnType = (
36+
factory: Factory.Type,
37+
successResponseNameList: string[],
38+
successResponseContentTypeList: string[],
39+
option: CodeGenerator.Option,
40+
) => {
3641
let objectType: ts.TypeNode = factory.TypeNode.create({
3742
type: "void",
3843
});
@@ -48,6 +53,9 @@ const generateResponseReturnType = (factory: Factory.Type, successResponseNameLi
4853

4954
// レスポンスが存在しないので Promise<void>
5055
if (successResponseNameList.length === 0) {
56+
if (option.sync) {
57+
return objectType;
58+
}
5159
return factory.TypeReferenceNode.create({
5260
name: "Promise",
5361
typeArguments: [objectType],
@@ -64,6 +72,13 @@ const generateResponseReturnType = (factory: Factory.Type, successResponseNameLi
6472
});
6573
}
6674

75+
if (option.sync) {
76+
return factory.IndexedAccessTypeNode.create({
77+
objectType,
78+
indexType,
79+
});
80+
}
81+
6782
return factory.TypeReferenceNode.create({
6883
name: "Promise",
6984
typeArguments: [
@@ -106,7 +121,7 @@ const methodTypeParameters = (factory: Factory.Type, params: CodeGeneratorParams
106121
*
107122
* }
108123
*/
109-
export const create = (factory: Factory.Type, params: CodeGeneratorParams): ts.MethodDeclaration => {
124+
export const create = (factory: Factory.Type, params: CodeGeneratorParams, option: CodeGenerator.Option): ts.MethodDeclaration => {
110125
const typeParameters: ts.TypeParameterDeclaration[] = methodTypeParameters(factory, params);
111126
const methodArguments: ts.ParameterDeclaration[] = [];
112127
const hasParamsArguments =
@@ -116,7 +131,7 @@ export const create = (factory: Factory.Type, params: CodeGeneratorParams): ts.M
116131
methodArguments.push(generateParams(factory, params));
117132
}
118133

119-
const returnType: ts.TypeNode = generateResponseReturnType(factory, params.responseSuccessNames, params.successResponseContentTypes);
134+
const returnType: ts.TypeNode = generateResponseReturnType(factory, params.responseSuccessNames, params.successResponseContentTypes, option);
120135

121136
methodArguments.push(
122137
factory.ParameterDeclaration.create({
@@ -131,7 +146,7 @@ export const create = (factory: Factory.Type, params: CodeGeneratorParams): ts.M
131146

132147
return factory.MethodDeclaration.create({
133148
name: params.functionName,
134-
async: true,
149+
async: !option.sync,
135150
parameters: methodArguments,
136151
comment: params.comment,
137152
deprecated: params.deprecated,
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import ts from "typescript";
22

33
import { Factory } from "../../CodeGenerator";
4-
import { CodeGeneratorParams } from "../../Converter/v3";
4+
import type { CodeGenerator, CodeGeneratorParams } from "../../Converter/v3";
55
import * as ApiClientInterface from "./ApiClientInterface";
66
import * as Class from "./Class";
77
import * as Constructor from "./Constructor";
88
import * as Method from "./Method";
99

1010
export { Method };
1111

12-
export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.Statement[] => {
12+
export const create = (factory: Factory.Type, list: CodeGeneratorParams[], option: CodeGenerator.Option): ts.Statement[] => {
1313
const methodList = list.map(params => {
14-
return Method.create(factory, params);
14+
return Method.create(factory, params, option);
1515
});
1616
const members = [Constructor.create(factory), ...methodList];
17-
return [...ApiClientInterface.create(factory, list), Class.create(factory, members)];
17+
return [...ApiClientInterface.create(factory, list, option), Class.create(factory, members)];
1818
};

src/DefaultCodeTemplate/index.ts

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

33
import * as TypeScriptCodeGenerator from "../CodeGenerator";
4-
import * as Converter from "../Converter";
4+
import type * as Converter from "../Converter";
55
import * as ApiClientArgument from "./ApiClientArgument";
66
import * as ApiClientClass from "./ApiClientClass";
77

8-
export const rewriteCodeAfterTypeDeclaration: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration = (
8+
export const rewriteCodeAfterTypeDeclaration: Converter.v3.CodeGenerator.RewriteCodeAfterTypeDeclaration = (
99
context: ts.TransformationContext,
1010
codeGeneratorParamsList: Converter.v3.CodeGeneratorParams[],
11+
option: Converter.v3.CodeGenerator.Option,
1112
): ts.Statement[] => {
1213
const statements: ts.Statement[] = [];
1314
const factory = TypeScriptCodeGenerator.Factory.create(context);
@@ -23,7 +24,7 @@ export const rewriteCodeAfterTypeDeclaration: Converter.v3.Generator.RewriteCode
2324
statements.push(typeDeclaration);
2425
}
2526
});
26-
ApiClientClass.create(factory, codeGeneratorParamsList).forEach(newStatement => {
27+
ApiClientClass.create(factory, codeGeneratorParamsList, option).forEach(newStatement => {
2728
statements.push(newStatement);
2829
});
2930
return statements;

src/index.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ export { Converter };
1212
export interface Params {
1313
entryPoint: string;
1414
option?: {
15-
rewriteCodeAfterTypeDeclaration?: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration;
15+
rewriteCodeAfterTypeDeclaration?: Converter.v3.CodeGenerator.RewriteCodeAfterTypeDeclaration;
16+
codeGenerator?: {
17+
/** default false */
18+
sync?: boolean;
19+
};
1620
};
1721
/** default: true */
1822
enableValidate?: boolean;
@@ -30,6 +34,25 @@ export interface Params {
3034
};
3135
}
3236

37+
const generateConvertOption = (filter: Params["filter"] = {}, option?: Params["option"]): Converter.v3.Option => {
38+
if (option) {
39+
return {
40+
rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
41+
allowOperationIds: filter.allowOperationIds,
42+
codeGeneratorOption: {
43+
sync: option.codeGenerator ? !!option.codeGenerator.sync : false,
44+
},
45+
};
46+
}
47+
return {
48+
rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
49+
allowOperationIds: filter.allowOperationIds,
50+
codeGeneratorOption: {
51+
sync: false,
52+
},
53+
};
54+
};
55+
3356
export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = true, log, filter = {} }: Params): string => {
3457
const schema = fileSystem.loadJsonOrYaml(entryPoint);
3558
const resolvedReferenceDocument = ResolveReference.resolve(entryPoint, entryPoint, JSON.parse(JSON.stringify(schema)));
@@ -38,15 +61,7 @@ export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = tr
3861
Validator.v3.validate(resolvedReferenceDocument, log && log.validator);
3962
}
4063

41-
const convertOption: Converter.v3.Option = option
42-
? {
43-
rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
44-
allowOperationIds: filter.allowOperationIds,
45-
}
46-
: {
47-
rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
48-
allowOperationIds: filter.allowOperationIds,
49-
};
64+
const convertOption = generateConvertOption(filter, option);
5065
const { createFunction, generateLeadingComment } = Converter.v3.create(entryPoint, schema, resolvedReferenceDocument, convertOption);
5166
return [generateLeadingComment(), TypeScriptCodeGenerator.generate(createFunction)].join(EOL + EOL + EOL);
5267
};

0 commit comments

Comments
 (0)