Skip to content

Commit

Permalink
feat: Support sync API Client generation (#22)
Browse files Browse the repository at this point in the history
* feat: Added option to output API Client in sync mode
  • Loading branch information
Himenon committed Mar 23, 2021
1 parent 103dde6 commit 4bbde34
Show file tree
Hide file tree
Showing 10 changed files with 520 additions and 38 deletions.
22 changes: 22 additions & 0 deletions scripts/testCodeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,31 @@ const gen = (name: string, enableValidate = true): void => {
console.log(`Generate Code : test/code/${name}.ts`);
};

const genSyncMode = (name: string, enableValidate = true): void => {
const params: CodeGenerator.Params = {
entryPoint: `test/${name}/index.yml`,
enableValidate,
option: {
codeGenerator: {
sync: true,
},
},
log: {
validator: {
displayLogLines: 1,
},
},
};
fs.mkdirSync("test/code", { recursive: true });
const code = CodeGenerator.generateTypeScriptCode(params);
fs.writeFileSync(`test/code/sync-${name}.ts`, code, { encoding: "utf-8" });
console.log(`Generate Code : test/code/sync-${name}.ts`);
};

const main = () => {
gen("api.test.domain");
gen("infer.domain", false);
genSyncMode("api.test.domain");
};

main();
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,14 @@ const generateCodeGeneratorParamsList = (
return params;
};

export interface Option {
sync: boolean;
}

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

export const generateApiClientCode = (
Expand All @@ -132,7 +137,8 @@ export const generateApiClientCode = (
converterContext: ConverterContext.Types,
rewriteCodeAfterTypeDeclaration: RewriteCodeAfterTypeDeclaration,
allowOperationIds: string[] | undefined,
option: Option,
): void => {
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext, allowOperationIds);
store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList));
store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList, option));
};
25 changes: 20 additions & 5 deletions src/Converter/v3/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ts from "typescript";

import * as TypeScriptCodeGenerator from "../../CodeGenerator";
import * as CodeGenerator from "./CodeGenerator";
import * as Comment from "./Comment";
import * as Headers from "./components/Headers";
import * as Parameters from "./components/Parameters";
Expand All @@ -9,26 +10,32 @@ import * as RequestBodies from "./components/RequestBodies";
import * as Responses from "./components/Responses";
import * as Schemas from "./components/Schemas";
import * as ConvertContext from "./ConverterContext";
import * as Generator from "./Generator";
import * as Name from "./Name";
import * as Paths from "./paths";
import { Store } from "./store";
import * as TypeNodeContext from "./TypeNodeContext";
import { CodeGeneratorParams, OpenApi, PickedParameter } from "./types";

export { OpenApi, Generator, CodeGeneratorParams, PickedParameter, Name };
export { OpenApi, CodeGenerator, CodeGeneratorParams, PickedParameter, Name };

export interface Type {
generateLeadingComment: () => string;
createFunction: TypeScriptCodeGenerator.CreateFunction;
codeGeneratorOption: CodeGenerator.Option;
}

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

rewriteCodeAfterTypeDeclaration: CodeGenerator.RewriteCodeAfterTypeDeclaration;
/**
*
*/
codeGeneratorOption: CodeGenerator.Option;
/**
* List of operationId to be used
*/
allowOperationIds?: string[];
}

Expand Down Expand Up @@ -99,13 +106,21 @@ 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.rewriteCodeAfterTypeDeclaration, option.allowOperationIds);
CodeGenerator.generateApiClientCode(
store,
context,
converterContext,
option.rewriteCodeAfterTypeDeclaration,
option.allowOperationIds,
option.codeGeneratorOption,
);
}
return store.getRootStatements();
};

return {
createFunction,
generateLeadingComment: () => Comment.generateLeading(rootSchema),
codeGeneratorOption: option.codeGeneratorOption,
};
};
26 changes: 16 additions & 10 deletions src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ts from "typescript";

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

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

Expand Down Expand Up @@ -104,7 +104,7 @@ const createObjectLikeInterface = (factory: Factory.Type) => {
});
};

export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.Statement[] => {
export const create = (factory: Factory.Type, list: CodeGeneratorParams[], option: CodeGenerator.Option): ts.Statement[] => {
const objectLikeOrAnyType = factory.UnionTypeNode.create({
typeNodes: [
factory.TypeReferenceNode.create({
Expand Down Expand Up @@ -163,6 +163,19 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
}),
});

const returnType = option.sync
? factory.TypeReferenceNode.create({
name: "T",
})
: factory.TypeReferenceNode.create({
name: "Promise",
typeArguments: [
factory.TypeReferenceNode.create({
name: "T",
}),
],
});

const functionType = factory.FunctionTypeNode.create({
typeParameters: [
factory.TypeParameterDeclaration.create({
Expand All @@ -173,14 +186,7 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
}),
],
parameters: [httpMethod, url, headers, requestBody, queryParameters, options],
type: factory.TypeReferenceNode.create({
name: "Promise",
typeArguments: [
factory.TypeReferenceNode.create({
name: "T",
}),
],
}),
type: returnType,
});

const requestFunction = factory.PropertySignature.create({
Expand Down
25 changes: 20 additions & 5 deletions src/DefaultCodeTemplate/ApiClientClass/Method.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ts from "typescript";

import { Factory } from "../../CodeGenerator";
import { CodeGeneratorParams } from "../../Converter/v3";
import type { CodeGenerator, CodeGeneratorParams } from "../../Converter/v3";
import * as MethodBody from "./MethodBody";

export { MethodBody };
Expand Down Expand Up @@ -32,7 +32,12 @@ const generateParams = (factory: Factory.Type, params: CodeGeneratorParams) => {
});
};

const generateResponseReturnType = (factory: Factory.Type, successResponseNameList: string[], successResponseContentTypeList: string[]) => {
const generateResponseReturnType = (
factory: Factory.Type,
successResponseNameList: string[],
successResponseContentTypeList: string[],
option: CodeGenerator.Option,
) => {
let objectType: ts.TypeNode = factory.TypeNode.create({
type: "void",
});
Expand All @@ -48,6 +53,9 @@ const generateResponseReturnType = (factory: Factory.Type, successResponseNameLi

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

if (option.sync) {
return factory.IndexedAccessTypeNode.create({
objectType,
indexType,
});
}

return factory.TypeReferenceNode.create({
name: "Promise",
typeArguments: [
Expand Down Expand Up @@ -106,7 +121,7 @@ const methodTypeParameters = (factory: Factory.Type, params: CodeGeneratorParams
*
* }
*/
export const create = (factory: Factory.Type, params: CodeGeneratorParams): ts.MethodDeclaration => {
export const create = (factory: Factory.Type, params: CodeGeneratorParams, option: CodeGenerator.Option): ts.MethodDeclaration => {
const typeParameters: ts.TypeParameterDeclaration[] = methodTypeParameters(factory, params);
const methodArguments: ts.ParameterDeclaration[] = [];
const hasParamsArguments =
Expand All @@ -116,7 +131,7 @@ export const create = (factory: Factory.Type, params: CodeGeneratorParams): ts.M
methodArguments.push(generateParams(factory, params));
}

const returnType: ts.TypeNode = generateResponseReturnType(factory, params.responseSuccessNames, params.successResponseContentTypes);
const returnType: ts.TypeNode = generateResponseReturnType(factory, params.responseSuccessNames, params.successResponseContentTypes, option);

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

return factory.MethodDeclaration.create({
name: params.functionName,
async: true,
async: !option.sync,
parameters: methodArguments,
comment: params.comment,
deprecated: params.deprecated,
Expand Down
8 changes: 4 additions & 4 deletions src/DefaultCodeTemplate/ApiClientClass/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import ts from "typescript";

import { Factory } from "../../CodeGenerator";
import { CodeGeneratorParams } from "../../Converter/v3";
import type { CodeGenerator, CodeGeneratorParams } from "../../Converter/v3";
import * as ApiClientInterface from "./ApiClientInterface";
import * as Class from "./Class";
import * as Constructor from "./Constructor";
import * as Method from "./Method";

export { Method };

export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.Statement[] => {
export const create = (factory: Factory.Type, list: CodeGeneratorParams[], option: CodeGenerator.Option): ts.Statement[] => {
const methodList = list.map(params => {
return Method.create(factory, params);
return Method.create(factory, params, option);
});
const members = [Constructor.create(factory), ...methodList];
return [...ApiClientInterface.create(factory, list), Class.create(factory, members)];
return [...ApiClientInterface.create(factory, list, option), Class.create(factory, members)];
};
7 changes: 4 additions & 3 deletions src/DefaultCodeTemplate/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import ts from "typescript";

import * as TypeScriptCodeGenerator from "../CodeGenerator";
import * as Converter from "../Converter";
import type * as Converter from "../Converter";
import * as ApiClientArgument from "./ApiClientArgument";
import * as ApiClientClass from "./ApiClientClass";

export const rewriteCodeAfterTypeDeclaration: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration = (
export const rewriteCodeAfterTypeDeclaration: Converter.v3.CodeGenerator.RewriteCodeAfterTypeDeclaration = (
context: ts.TransformationContext,
codeGeneratorParamsList: Converter.v3.CodeGeneratorParams[],
option: Converter.v3.CodeGenerator.Option,
): ts.Statement[] => {
const statements: ts.Statement[] = [];
const factory = TypeScriptCodeGenerator.Factory.create(context);
Expand All @@ -23,7 +24,7 @@ export const rewriteCodeAfterTypeDeclaration: Converter.v3.Generator.RewriteCode
statements.push(typeDeclaration);
}
});
ApiClientClass.create(factory, codeGeneratorParamsList).forEach(newStatement => {
ApiClientClass.create(factory, codeGeneratorParamsList, option).forEach(newStatement => {
statements.push(newStatement);
});
return statements;
Expand Down
35 changes: 25 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export { Converter };
export interface Params {
entryPoint: string;
option?: {
rewriteCodeAfterTypeDeclaration?: Converter.v3.Generator.RewriteCodeAfterTypeDeclaration;
rewriteCodeAfterTypeDeclaration?: Converter.v3.CodeGenerator.RewriteCodeAfterTypeDeclaration;
codeGenerator?: {
/** default false */
sync?: boolean;
};
};
/** default: true */
enableValidate?: boolean;
Expand All @@ -30,6 +34,25 @@ export interface Params {
};
}

const generateConvertOption = (filter: Params["filter"] = {}, option?: Params["option"]): Converter.v3.Option => {
if (option) {
return {
rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
allowOperationIds: filter.allowOperationIds,
codeGeneratorOption: {
sync: option.codeGenerator ? !!option.codeGenerator.sync : false,
},
};
}
return {
rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
allowOperationIds: filter.allowOperationIds,
codeGeneratorOption: {
sync: false,
},
};
};

export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = true, log, filter = {} }: Params): string => {
const schema = fileSystem.loadJsonOrYaml(entryPoint);
const resolvedReferenceDocument = ResolveReference.resolve(entryPoint, entryPoint, JSON.parse(JSON.stringify(schema)));
Expand All @@ -38,15 +61,7 @@ export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = tr
Validator.v3.validate(resolvedReferenceDocument, log && log.validator);
}

const convertOption: Converter.v3.Option = option
? {
rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
allowOperationIds: filter.allowOperationIds,
}
: {
rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
allowOperationIds: filter.allowOperationIds,
};
const convertOption = generateConvertOption(filter, option);
const { createFunction, generateLeadingComment } = Converter.v3.create(entryPoint, schema, resolvedReferenceDocument, convertOption);
return [generateLeadingComment(), TypeScriptCodeGenerator.generate(createFunction)].join(EOL + EOL + EOL);
};

0 comments on commit 4bbde34

Please sign in to comment.