Skip to content

Commit

Permalink
feat: Support build github-rest-api-description (#16)
Browse files Browse the repository at this point in the history
BREAKING_CHANGE

* Remove MapLike -> use Record
* Corresponds to the case where a character string such as _ or - is included
* Closes: #12 #13 #14 #15
  • Loading branch information
Himenon committed Feb 1, 2021
1 parent a3c77ac commit 0323f9f
Show file tree
Hide file tree
Showing 40 changed files with 904 additions and 258 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = {
},
plugins: ["@typescript-eslint"],
rules: {
"no-unused-vars": "warn",
"no-unused-vars": "off",
"@typescript-eslint/ban-types": "warn",
"@typescript-eslint/no-namespace": "off",
},
Expand Down
14 changes: 10 additions & 4 deletions scripts/testCodeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import * as fs from "fs";

import * as CodeGenerator from "../lib";

const main = () => {
const gen = (name: string, enableValidate = true): void => {
const params: CodeGenerator.Params = {
entryPoint: "test/api.test.domain/index.yml",
entryPoint: `test/${name}/index.yml`,
enableValidate,
log: {
validator: {
displayLogLines: 1,
Expand All @@ -13,8 +14,13 @@ const main = () => {
};
fs.mkdirSync("test/code", { recursive: true });
const code = CodeGenerator.generateTypeScriptCode(params);
fs.writeFileSync("test/code/api.test.domain.ts", code, { encoding: "utf-8" });
console.log(`Generate Code : test/code/api.test.domain.ts`);
fs.writeFileSync(`test/code/${name}.ts`, code, { encoding: "utf-8" });
console.log(`Generate Code : test/code/${name}.ts`);
};

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

main();
87 changes: 87 additions & 0 deletions src/Converter/v3/ConverterContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as Name from "./Name";
/**
* ユーザーが利用できる各種変換オプション
*/
// export interface Options {

// }

export interface Types {
/**
* operationIdに対するescape
*/
escapeOperationIdText: (operationId: string) => string;
/**
* interface/namespace/typeAliasのnameをescapeする
* import/exportなどの予約語も裁く
*/
escapeDeclarationText: (text: string) => string;
/**
* 非破壊: PropertySignatureのname用のescape
*/
escapePropertySignatureName: (text: string) => string;
/**
* 破壊: TypeReferenceのname用のescape
*/
escapeTypeReferenceNodeName: (text: string) => string;
generateResponseName: (operationId: string, statusCode: string) => string;
generateArgumentParamsTypeDeclaration: (operationId: string) => string;
generateRequestContentTypeName: (operationId: string) => string;
generateResponseContentTypeName: (operationId: string) => string;
generateParameterName: (operationId: string) => string;
generateRequestBodyName: (operationId: string) => string;
generateFunctionName: (operationId: string) => string;
}

/**
* ユーザーが利用できる各種変換オプション
*/
export const create = (): Types => {
const convertReservedWord = (word: string): string => {
if (["import", "export"].includes(word)) {
return word + "_";
}
return word;
};
const convertString = (text: string): string => {
if (Name.isAvailableVariableName(text)) {
return text;
}
return text.replace(/-/g, "$").replace(/\//g, "$");
};
return {
escapeOperationIdText: (operationId: string): string => {
return convertString(operationId);
},
escapeDeclarationText: (text: string) => {
return convertReservedWord(convertString(text));
},
escapePropertySignatureName: (text: string) => {
return Name.escapeText(text);
},
escapeTypeReferenceNodeName: (text: string) => {
return convertString(text);
},
generateResponseName: (operationId: string, statusCode: string): string => {
return Name.responseName(convertString(operationId), statusCode);
},
generateArgumentParamsTypeDeclaration: (operationId: string) => {
return Name.argumentParamsTypeDeclaration(convertString(operationId));
},
generateRequestContentTypeName: (operationId: string) => {
return Name.requestContentType(convertString(operationId));
},
generateResponseContentTypeName: (operationId: string) => {
return Name.responseContentType(convertString(operationId));
},
generateParameterName: (operationId: string) => {
return Name.parameterName(convertString(operationId));
},
generateRequestBodyName: (operationId: string) => {
return Name.requestBodyName(convertString(operationId));
},
generateFunctionName: (operationId: string) => {
return convertString(operationId);
},
};
};
27 changes: 19 additions & 8 deletions src/Converter/v3/Generator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ts from "typescript";

import { Factory } from "../../CodeGenerator";
import * as Name from "./Name";
import * as ConverterContext from "./ConverterContext";
import { Store } from "./store";
import { CodeGeneratorParams, OpenApi, PickedParameter } from "./types";

Expand Down Expand Up @@ -52,24 +51,31 @@ const hasQueryParameters = (parameters?: OpenApi.Parameter[]): boolean => {
return parameters.filter(parameter => parameter.in === "query").length > 0;
};

const generateCodeGeneratorParamsList = (store: Store.Type): CodeGeneratorParams[] => {
const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: ConverterContext.Types): CodeGeneratorParams[] => {
const operationState = store.getNoReferenceOperationState();
const params: CodeGeneratorParams[] = [];
Object.entries(operationState).forEach(([operationId, item]) => {
const responseSuccessNames = extractSuccessStatusCode(item.responses).map(statusCode => Name.responseName(operationId, statusCode));
const responseSuccessNames = extractSuccessStatusCode(item.responses).map(statusCode =>
converterContext.generateResponseName(operationId, statusCode),
);
const requestContentTypeList = item.requestBody ? getRequestContentTypeList(item.requestBody) : [];
const responseSuccessContentTypes = getSuccessResponseContentTypeList(item.responses);
const hasOver2RequestContentTypes = requestContentTypeList.length > 1;
const hasOver2SuccessNames = responseSuccessNames.length > 1;

const formatParams: CodeGeneratorParams = {
operationId: operationId,
rawRequestUri: item.requestUri,
httpMethod: item.httpMethod,
argumentParamsTypeDeclaration: Name.argumentParamsTypeDeclaration(operationId),
argumentParamsTypeDeclaration: converterContext.generateArgumentParamsTypeDeclaration(operationId),
// function
functionName: operationId,
functionName: converterContext.generateFunctionName(operationId),
comment: item.comment,
deprecated: item.deprecated,
requestContentTypeName: converterContext.generateRequestContentTypeName(operationId),
responseContentTypeName: converterContext.generateResponseContentTypeName(operationId),
parameterName: converterContext.generateParameterName(operationId),
requestBodyName: converterContext.generateRequestBodyName(operationId),
//
hasRequestBody: !!item.requestBody,
hasParameter: item.parameters ? item.parameters.length > 0 : false,
Expand Down Expand Up @@ -99,7 +105,12 @@ const generateCodeGeneratorParamsList = (store: Store.Type): CodeGeneratorParams

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

export const generateApiClientCode = (store: Store.Type, context: ts.TransformationContext, makeApiClient: MakeApiClientFunction): void => {
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store);
export const generateApiClientCode = (
store: Store.Type,
context: ts.TransformationContext,
converterContext: ConverterContext.Types,
makeApiClient: MakeApiClientFunction,
): void => {
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext);
store.addAdditionalStatement(makeApiClient(context, codeGeneratorParamsList));
};
4 changes: 4 additions & 0 deletions src/Converter/v3/Guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const isObjectSchema = (schema: Types.OpenApi.Schema): schema is Types.Ob
return schema.type === "object";
};

export const isHasNoMembersObject = (schema: Types.OpenApi.Schema): boolean => {
return Object.keys(schema).length === 0;
};

export const isArraySchema = (schema: Types.OpenApi.Schema): schema is Types.ArraySchema => {
return schema.type === "array";
};
Expand Down
27 changes: 27 additions & 0 deletions src/Converter/v3/InferredType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as Types from "./types";

export const getInferredType = (schema: Types.OpenApi.Schema): Types.OpenApi.Schema | undefined => {
if (schema.type || schema.oneOf || schema.allOf || schema.anyOf) {
return schema;
}
// type: arrayを指定せずに、itemsのみを指定している場合に type array変換する
if (schema.items) {
return { ...schema, type: "array" };
}
// type: string/numberを指定せずに、enumのみを指定している場合に type array変換する
if (schema.enum) {
return { ...schema, type: "string" };
}
// type: objectを指定せずに、propertiesのみを指定している場合に type object変換する
if (schema.properties) {
return { ...schema, type: "object" };
}
// type: object, propertiesを指定せずに、requiredのみを指定している場合に type object変換する
if (schema.required) {
const properties = schema.required.reduce((s, name) => {
return { ...s, [name]: { type: "any" } };
}, {});
return { ...schema, type: "object", properties };
}
return undefined;
};
4 changes: 2 additions & 2 deletions src/Converter/v3/Name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export const requestContentType = (operationId: string): string => `RequestConte
export const responseContentType = (operationId: string): string => `ResponseContentType$${operationId}`;

export const isAvailableVariableName = (text: string): boolean => {
return /^[A-Za-z_\s]+$/.test(text);
return /^[A-Za-z_0-9\s]+$/.test(text);
};

export const escapeText = (text: string) => {
export const escapeText = (text: string): string => {
if (isAvailableVariableName(text)) {
return text;
}
Expand Down
44 changes: 32 additions & 12 deletions src/Converter/v3/Context.ts → src/Converter/v3/TypeNodeContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ts from "typescript";

import * as TypeScriptCodeGenerator from "../../CodeGenerator";
import { DevelopmentError } from "../../Exception";
import * as ConverterContext from "./ConverterContext";
import { Store } from "./store";
import * as ToTypeNode from "./toTypeNode";

Expand Down Expand Up @@ -72,7 +73,12 @@ const calculateReferencePath = (store: Store.Type, base: string, pathArray: stri
};
};

export const create = (entryPoint: string, store: Store.Type, factory: TypeScriptCodeGenerator.Factory.Type): ToTypeNode.Context => {
export const create = (
entryPoint: string,
store: Store.Type,
factory: TypeScriptCodeGenerator.Factory.Type,
converterContext: ConverterContext.Types,
): ToTypeNode.Context => {
const resolveReferencePath: ToTypeNode.Context["resolveReferencePath"] = (currentPoint, referencePath) => {
const { pathArray, base } = generatePath(entryPoint, currentPoint, referencePath);
return calculateReferencePath(store, base, pathArray);
Expand All @@ -82,10 +88,17 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
return;
}
if (reference.type === "remote") {
const typeNode = ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
setReferenceHandler,
resolveReferencePath,
});
const typeNode = ToTypeNode.convert(
entryPoint,
reference.referencePoint,
factory,
reference.data,
{
setReferenceHandler,
resolveReferencePath,
},
converterContext,
);
if (ts.isTypeLiteralNode(typeNode)) {
store.addStatement(reference.path, {
kind: "interface",
Expand All @@ -99,11 +112,18 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
} else {
const value = factory.TypeAliasDeclaration.create({
export: true,
name: reference.name,
type: ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
setReferenceHandler,
resolveReferencePath,
}),
name: converterContext.escapeDeclarationText(reference.name),
type: ToTypeNode.convert(
entryPoint,
reference.referencePoint,
factory,
reference.data,
{
setReferenceHandler,
resolveReferencePath,
},
converterContext,
),
});
store.addStatement(reference.path, {
name: reference.name,
Expand All @@ -116,9 +136,9 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
const { maybeResolvedName } = resolveReferencePath(currentPoint, reference.path);
const value = factory.TypeAliasDeclaration.create({
export: true,
name: reference.name,
name: converterContext.escapeDeclarationText(reference.name),
type: factory.TypeReferenceNode.create({
name: maybeResolvedName,
name: converterContext.escapeTypeReferenceNodeName(maybeResolvedName),
}),
});
store.addStatement(reference.path, {
Expand Down
Loading

0 comments on commit 0323f9f

Please sign in to comment.