Skip to content

Commit 0323f9f

Browse files
authored
feat: Support build github-rest-api-description (#16)
BREAKING_CHANGE * Remove MapLike -> use Record * Corresponds to the case where a character string such as _ or - is included * Closes: #12 #13 #14 #15
1 parent a3c77ac commit 0323f9f

40 files changed

+904
-258
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module.exports = {
1212
},
1313
plugins: ["@typescript-eslint"],
1414
rules: {
15-
"no-unused-vars": "warn",
15+
"no-unused-vars": "off",
1616
"@typescript-eslint/ban-types": "warn",
1717
"@typescript-eslint/no-namespace": "off",
1818
},

scripts/testCodeGen.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import * as fs from "fs";
22

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

5-
const main = () => {
5+
const gen = (name: string, enableValidate = true): void => {
66
const params: CodeGenerator.Params = {
7-
entryPoint: "test/api.test.domain/index.yml",
7+
entryPoint: `test/${name}/index.yml`,
8+
enableValidate,
89
log: {
910
validator: {
1011
displayLogLines: 1,
@@ -13,8 +14,13 @@ const main = () => {
1314
};
1415
fs.mkdirSync("test/code", { recursive: true });
1516
const code = CodeGenerator.generateTypeScriptCode(params);
16-
fs.writeFileSync("test/code/api.test.domain.ts", code, { encoding: "utf-8" });
17-
console.log(`Generate Code : test/code/api.test.domain.ts`);
17+
fs.writeFileSync(`test/code/${name}.ts`, code, { encoding: "utf-8" });
18+
console.log(`Generate Code : test/code/${name}.ts`);
19+
};
20+
21+
const main = () => {
22+
gen("api.test.domain");
23+
gen("infer.domain", false);
1824
};
1925

2026
main();

src/Converter/v3/ConverterContext.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import * as Name from "./Name";
2+
/**
3+
* ユーザーが利用できる各種変換オプション
4+
*/
5+
// export interface Options {
6+
7+
// }
8+
9+
export interface Types {
10+
/**
11+
* operationIdに対するescape
12+
*/
13+
escapeOperationIdText: (operationId: string) => string;
14+
/**
15+
* interface/namespace/typeAliasのnameをescapeする
16+
* import/exportなどの予約語も裁く
17+
*/
18+
escapeDeclarationText: (text: string) => string;
19+
/**
20+
* 非破壊: PropertySignatureのname用のescape
21+
*/
22+
escapePropertySignatureName: (text: string) => string;
23+
/**
24+
* 破壊: TypeReferenceのname用のescape
25+
*/
26+
escapeTypeReferenceNodeName: (text: string) => string;
27+
generateResponseName: (operationId: string, statusCode: string) => string;
28+
generateArgumentParamsTypeDeclaration: (operationId: string) => string;
29+
generateRequestContentTypeName: (operationId: string) => string;
30+
generateResponseContentTypeName: (operationId: string) => string;
31+
generateParameterName: (operationId: string) => string;
32+
generateRequestBodyName: (operationId: string) => string;
33+
generateFunctionName: (operationId: string) => string;
34+
}
35+
36+
/**
37+
* ユーザーが利用できる各種変換オプション
38+
*/
39+
export const create = (): Types => {
40+
const convertReservedWord = (word: string): string => {
41+
if (["import", "export"].includes(word)) {
42+
return word + "_";
43+
}
44+
return word;
45+
};
46+
const convertString = (text: string): string => {
47+
if (Name.isAvailableVariableName(text)) {
48+
return text;
49+
}
50+
return text.replace(/-/g, "$").replace(/\//g, "$");
51+
};
52+
return {
53+
escapeOperationIdText: (operationId: string): string => {
54+
return convertString(operationId);
55+
},
56+
escapeDeclarationText: (text: string) => {
57+
return convertReservedWord(convertString(text));
58+
},
59+
escapePropertySignatureName: (text: string) => {
60+
return Name.escapeText(text);
61+
},
62+
escapeTypeReferenceNodeName: (text: string) => {
63+
return convertString(text);
64+
},
65+
generateResponseName: (operationId: string, statusCode: string): string => {
66+
return Name.responseName(convertString(operationId), statusCode);
67+
},
68+
generateArgumentParamsTypeDeclaration: (operationId: string) => {
69+
return Name.argumentParamsTypeDeclaration(convertString(operationId));
70+
},
71+
generateRequestContentTypeName: (operationId: string) => {
72+
return Name.requestContentType(convertString(operationId));
73+
},
74+
generateResponseContentTypeName: (operationId: string) => {
75+
return Name.responseContentType(convertString(operationId));
76+
},
77+
generateParameterName: (operationId: string) => {
78+
return Name.parameterName(convertString(operationId));
79+
},
80+
generateRequestBodyName: (operationId: string) => {
81+
return Name.requestBodyName(convertString(operationId));
82+
},
83+
generateFunctionName: (operationId: string) => {
84+
return convertString(operationId);
85+
},
86+
};
87+
};

src/Converter/v3/Generator.ts

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

3-
import { Factory } from "../../CodeGenerator";
4-
import * as Name from "./Name";
3+
import * as ConverterContext from "./ConverterContext";
54
import { Store } from "./store";
65
import { CodeGeneratorParams, OpenApi, PickedParameter } from "./types";
76

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

55-
const generateCodeGeneratorParamsList = (store: Store.Type): CodeGeneratorParams[] => {
54+
const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: ConverterContext.Types): CodeGeneratorParams[] => {
5655
const operationState = store.getNoReferenceOperationState();
5756
const params: CodeGeneratorParams[] = [];
5857
Object.entries(operationState).forEach(([operationId, item]) => {
59-
const responseSuccessNames = extractSuccessStatusCode(item.responses).map(statusCode => Name.responseName(operationId, statusCode));
58+
const responseSuccessNames = extractSuccessStatusCode(item.responses).map(statusCode =>
59+
converterContext.generateResponseName(operationId, statusCode),
60+
);
6061
const requestContentTypeList = item.requestBody ? getRequestContentTypeList(item.requestBody) : [];
6162
const responseSuccessContentTypes = getSuccessResponseContentTypeList(item.responses);
6263
const hasOver2RequestContentTypes = requestContentTypeList.length > 1;
6364
const hasOver2SuccessNames = responseSuccessNames.length > 1;
65+
6466
const formatParams: CodeGeneratorParams = {
6567
operationId: operationId,
6668
rawRequestUri: item.requestUri,
6769
httpMethod: item.httpMethod,
68-
argumentParamsTypeDeclaration: Name.argumentParamsTypeDeclaration(operationId),
70+
argumentParamsTypeDeclaration: converterContext.generateArgumentParamsTypeDeclaration(operationId),
6971
// function
70-
functionName: operationId,
72+
functionName: converterContext.generateFunctionName(operationId),
7173
comment: item.comment,
7274
deprecated: item.deprecated,
75+
requestContentTypeName: converterContext.generateRequestContentTypeName(operationId),
76+
responseContentTypeName: converterContext.generateResponseContentTypeName(operationId),
77+
parameterName: converterContext.generateParameterName(operationId),
78+
requestBodyName: converterContext.generateRequestBodyName(operationId),
7379
//
7480
hasRequestBody: !!item.requestBody,
7581
hasParameter: item.parameters ? item.parameters.length > 0 : false,
@@ -99,7 +105,12 @@ const generateCodeGeneratorParamsList = (store: Store.Type): CodeGeneratorParams
99105

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

102-
export const generateApiClientCode = (store: Store.Type, context: ts.TransformationContext, makeApiClient: MakeApiClientFunction): void => {
103-
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store);
108+
export const generateApiClientCode = (
109+
store: Store.Type,
110+
context: ts.TransformationContext,
111+
converterContext: ConverterContext.Types,
112+
makeApiClient: MakeApiClientFunction,
113+
): void => {
114+
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext);
104115
store.addAdditionalStatement(makeApiClient(context, codeGeneratorParamsList));
105116
};

src/Converter/v3/Guard.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export const isObjectSchema = (schema: Types.OpenApi.Schema): schema is Types.Ob
1616
return schema.type === "object";
1717
};
1818

19+
export const isHasNoMembersObject = (schema: Types.OpenApi.Schema): boolean => {
20+
return Object.keys(schema).length === 0;
21+
};
22+
1923
export const isArraySchema = (schema: Types.OpenApi.Schema): schema is Types.ArraySchema => {
2024
return schema.type === "array";
2125
};

src/Converter/v3/InferredType.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as Types from "./types";
2+
3+
export const getInferredType = (schema: Types.OpenApi.Schema): Types.OpenApi.Schema | undefined => {
4+
if (schema.type || schema.oneOf || schema.allOf || schema.anyOf) {
5+
return schema;
6+
}
7+
// type: arrayを指定せずに、itemsのみを指定している場合に type array変換する
8+
if (schema.items) {
9+
return { ...schema, type: "array" };
10+
}
11+
// type: string/numberを指定せずに、enumのみを指定している場合に type array変換する
12+
if (schema.enum) {
13+
return { ...schema, type: "string" };
14+
}
15+
// type: objectを指定せずに、propertiesのみを指定している場合に type object変換する
16+
if (schema.properties) {
17+
return { ...schema, type: "object" };
18+
}
19+
// type: object, propertiesを指定せずに、requiredのみを指定している場合に type object変換する
20+
if (schema.required) {
21+
const properties = schema.required.reduce((s, name) => {
22+
return { ...s, [name]: { type: "any" } };
23+
}, {});
24+
return { ...schema, type: "object", properties };
25+
}
26+
return undefined;
27+
};

src/Converter/v3/Name.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ export const requestContentType = (operationId: string): string => `RequestConte
66
export const responseContentType = (operationId: string): string => `ResponseContentType$${operationId}`;
77

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

12-
export const escapeText = (text: string) => {
12+
export const escapeText = (text: string): string => {
1313
if (isAvailableVariableName(text)) {
1414
return text;
1515
}

src/Converter/v3/Context.ts renamed to src/Converter/v3/TypeNodeContext.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ts from "typescript";
44

55
import * as TypeScriptCodeGenerator from "../../CodeGenerator";
66
import { DevelopmentError } from "../../Exception";
7+
import * as ConverterContext from "./ConverterContext";
78
import { Store } from "./store";
89
import * as ToTypeNode from "./toTypeNode";
910

@@ -72,7 +73,12 @@ const calculateReferencePath = (store: Store.Type, base: string, pathArray: stri
7273
};
7374
};
7475

75-
export const create = (entryPoint: string, store: Store.Type, factory: TypeScriptCodeGenerator.Factory.Type): ToTypeNode.Context => {
76+
export const create = (
77+
entryPoint: string,
78+
store: Store.Type,
79+
factory: TypeScriptCodeGenerator.Factory.Type,
80+
converterContext: ConverterContext.Types,
81+
): ToTypeNode.Context => {
7682
const resolveReferencePath: ToTypeNode.Context["resolveReferencePath"] = (currentPoint, referencePath) => {
7783
const { pathArray, base } = generatePath(entryPoint, currentPoint, referencePath);
7884
return calculateReferencePath(store, base, pathArray);
@@ -82,10 +88,17 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
8288
return;
8389
}
8490
if (reference.type === "remote") {
85-
const typeNode = ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
86-
setReferenceHandler,
87-
resolveReferencePath,
88-
});
91+
const typeNode = ToTypeNode.convert(
92+
entryPoint,
93+
reference.referencePoint,
94+
factory,
95+
reference.data,
96+
{
97+
setReferenceHandler,
98+
resolveReferencePath,
99+
},
100+
converterContext,
101+
);
89102
if (ts.isTypeLiteralNode(typeNode)) {
90103
store.addStatement(reference.path, {
91104
kind: "interface",
@@ -99,11 +112,18 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
99112
} else {
100113
const value = factory.TypeAliasDeclaration.create({
101114
export: true,
102-
name: reference.name,
103-
type: ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
104-
setReferenceHandler,
105-
resolveReferencePath,
106-
}),
115+
name: converterContext.escapeDeclarationText(reference.name),
116+
type: ToTypeNode.convert(
117+
entryPoint,
118+
reference.referencePoint,
119+
factory,
120+
reference.data,
121+
{
122+
setReferenceHandler,
123+
resolveReferencePath,
124+
},
125+
converterContext,
126+
),
107127
});
108128
store.addStatement(reference.path, {
109129
name: reference.name,
@@ -116,9 +136,9 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
116136
const { maybeResolvedName } = resolveReferencePath(currentPoint, reference.path);
117137
const value = factory.TypeAliasDeclaration.create({
118138
export: true,
119-
name: reference.name,
139+
name: converterContext.escapeDeclarationText(reference.name),
120140
type: factory.TypeReferenceNode.create({
121-
name: maybeResolvedName,
141+
name: converterContext.escapeTypeReferenceNodeName(maybeResolvedName),
122142
}),
123143
});
124144
store.addStatement(reference.path, {

0 commit comments

Comments
 (0)