Skip to content

Commit 88cba4c

Browse files
committed
feature: ability to customize resolving process of the extracting type names (extractingOptions nodejs option)
docs: update docs for `extraTemplates` option fix: problem with default name of single api file (Api.ts) fix: problem based with tuple types (#445) fix: problem with `defaultResponseType` declaration type
1 parent f69b156 commit 88cba4c

File tree

15 files changed

+244
-73
lines changed

15 files changed

+244
-73
lines changed

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,31 @@ typeSuffix?: string;
2222
enumKeyPrefix?: string;
2323
enumKeySuffix?: string;
2424
```
25+
feature: ability to customize resolving process of the extracting type names (`extractingOptions` nodejs option)
26+
```ts
27+
extractingOptions = {
28+
// requestBodySuffix: ["Payload", "Body", "Input"],
29+
// or
30+
// requestBodyNameResolver: (typeName, reservedNames) => string;
31+
32+
// requestParamsSuffix: ["Params"],
33+
// or
34+
// requestParamsNameResolver: (typeName, reservedNames) => string;
35+
36+
// responseBodySuffix: ["Data", "Result", "Output"],
37+
// or
38+
// responseBodyNameResolver: (typeName, reservedNames) => string;
39+
40+
// responseErrorSuffix: ["Error", "Fail", "Fails", "ErrorData", "HttpError", "BadResponse"],
41+
// or
42+
// responseErrorNameResolver: (typeName, reservedNames) => string;
43+
}
44+
```
2545
docs: update docs for `extraTemplates` option
46+
fix: problem with default name of single api file (Api.ts)
47+
fix: problem based with tuple types (#445)
48+
fix: problem with `defaultResponseType` declaration type
49+
2650

2751
# 11.1.3
2852

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,15 @@ generateApi({
139139
generateUnionEnums: false,
140140
typePrefix: '',
141141
typeSuffix: '',
142+
enumKeyPrefix: '',
143+
enumKeySuffix: '',
142144
addReadonly: false,
145+
extractingOptions: {
146+
requestBodySuffix: ["Payload", "Body", "Input"],
147+
requestParamsSuffix: ["Params"],
148+
responseBodySuffix: ["Data", "Result", "Output"],
149+
responseErrorSuffix: ["Error", "Fail", "Fails", "ErrorData", "HttpError", "BadResponse"],
150+
},
143151
/** allow to generate extra files based with this extra templates, see more below */
144152
extraTemplates: [],
145153
anotherArrayType: false,

index.d.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ interface GenerateApiParamsBase {
106106
/**
107107
* default type for empty response schema (default: "void")
108108
*/
109-
defaultResponseType?: boolean;
109+
defaultResponseType?: string;
110110
/**
111111
* Ability to send HttpClient instance to Api constructor
112112
*/
@@ -151,6 +151,7 @@ interface GenerateApiParamsBase {
151151
typePrefix?: string;
152152
/** suffix string value for type names */
153153
typeSuffix?: string;
154+
extractingOptions?: Partial<ExtractingOptions>;
154155
}
155156

156157
type CodeGenConstruct = {
@@ -421,6 +422,17 @@ export enum SCHEMA_TYPES {
421422

422423
type MAIN_SCHEMA_TYPES = SCHEMA_TYPES.PRIMITIVE | SCHEMA_TYPES.OBJECT | SCHEMA_TYPES.ENUM;
423424

425+
type ExtractingOptions = {
426+
requestBodySuffix: string[];
427+
responseBodySuffix: string[];
428+
responseErrorSuffix: string[];
429+
requestParamsSuffix: string[];
430+
requestBodyNameResolver: (name: string, reservedNames: string) => string | undefined;
431+
responseBodyNameResolver: (name: string, reservedNames: string) => string | undefined;
432+
responseErrorNameResolver: (name: string, reservedNames: string) => string | undefined;
433+
requestParamsNameResolver: (name: string, reservedNames: string) => string | undefined;
434+
};
435+
424436
export interface GenerateApiConfiguration {
425437
apiConfig: {
426438
baseUrl: string;
@@ -470,7 +482,7 @@ export interface GenerateApiConfiguration {
470482
extractEnums: boolean;
471483
fixInvalidTypeNamePrefix: string;
472484
fixInvalidEnumKeyPrefix: string;
473-
defaultResponseType: boolean;
485+
defaultResponseType: string;
474486
toJS: boolean;
475487
disableThrowOnError: boolean;
476488
silent: boolean;
@@ -502,6 +514,7 @@ export interface GenerateApiConfiguration {
502514
routeNameDuplicatesMap: Map<string, string>;
503515
apiClassName: string;
504516
requestOptions?: import("node-fetch").RequestInit;
517+
extractingOptions: ExtractingOptions;
505518
};
506519
modelTypes: ModelType[];
507520
rawModelTypes: SchemaComponent[];

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const program = cli({
3737
{
3838
flags: "-n, --name <string>",
3939
description: "name of output typescript api file",
40-
default: `${codeGenBaseConfig.apiClassName}.ts`,
40+
default: codeGenBaseConfig.fileName,
4141
},
4242
{
4343
flags: "-t, --templates <string>",

src/code-gen-process.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class CodeGenProcess {
8181
this.templates,
8282
this.typeName,
8383
);
84+
this.config.componentTypeNameResolver.logger = this.logger;
8485
}
8586

8687
async start() {
@@ -169,9 +170,11 @@ class CodeGenProcess {
169170

170171
if (this.fileSystem.pathIsExist(this.config.output)) {
171172
if (this.config.cleanOutput) {
173+
this.logger.debug(`cleaning dir ${this.config.output}`);
172174
this.fileSystem.cleanDir(this.config.output);
173175
}
174176
} else {
177+
this.logger.debug(`path ${this.config.output} is not exist. creating dir by this path`);
175178
this.fileSystem.createDir(this.config.output);
176179
}
177180

@@ -398,15 +401,11 @@ class CodeGenProcess {
398401
if (configuration.translateToJavaScript) {
399402
const { sourceContent, declarationContent } = translateToJS(`${fixedFileName}${ts.Extension.Ts}`, content);
400403

401-
if (this.config.debug) {
402-
console.info("generating output for", `${fixedFileName}${ts.Extension.Js}`);
403-
console.info(sourceContent);
404-
}
404+
this.logger.debug("generating output for", `${fixedFileName}${ts.Extension.Js}`);
405+
this.logger.debug(sourceContent);
405406

406-
if (this.config.debug) {
407-
console.info("generating output for", `${fixedFileName}${ts.Extension.Dts}`);
408-
console.info(declarationContent);
409-
}
407+
this.logger.debug("generating output for", `${fixedFileName}${ts.Extension.Js}`);
408+
this.logger.debug(declarationContent);
410409

411410
return {
412411
name: `${fixedFileName}${ts.Extension.Js}`,
@@ -418,10 +417,8 @@ class CodeGenProcess {
418417
};
419418
}
420419

421-
if (this.config.debug) {
422-
console.info("generating output for", `${fixedFileName}${ts.Extension.Ts}`);
423-
console.info(content);
424-
}
420+
this.logger.debug("generating output for", `${fixedFileName}${ts.Extension.Js}`);
421+
this.logger.debug(content);
425422

426423
return {
427424
name: `${fixedFileName}${ts.Extension.Ts}`,

src/configuration.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class CodeGenConfig {
134134
enumKeyPrefix = "";
135135
enumKeySuffix = "";
136136
patch = false;
137-
componentTypeNameResolver = new ComponentTypeNameResolver([]);
137+
componentTypeNameResolver = new ComponentTypeNameResolver(null, []);
138138
/** name of the main exported class */
139139
apiClassName = "Api";
140140
debug = false;
@@ -149,7 +149,7 @@ class CodeGenConfig {
149149
url = "";
150150
cleanOutput = false;
151151
spec = null;
152-
fileName = "";
152+
fileName = "Api.ts";
153153
authorizationToken = void 0;
154154
requestOptions = null;
155155

@@ -160,6 +160,14 @@ class CodeGenConfig {
160160

161161
successResponseStatusRange = [200, 299];
162162

163+
/** @type {ExtractingOptions} */
164+
extractingOptions = {
165+
requestBodySuffix: ["Payload", "Body", "Input"],
166+
requestParamsSuffix: ["Params"],
167+
responseBodySuffix: ["Data", "Result", "Output"],
168+
responseErrorSuffix: ["Error", "Fail", "Fails", "ErrorData", "HttpError", "BadResponse"],
169+
};
170+
163171
Ts = {
164172
Keyword: _.cloneDeep(TsKeyword),
165173
CodeGenKeyword: _.cloneDeep(TsCodeGenKeyword),
@@ -244,6 +252,12 @@ class CodeGenConfig {
244252
TypeWithGeneric: (typeName, genericArgs) => {
245253
return `${typeName}${genericArgs.length ? `<${genericArgs.join(",")}>` : ""}`;
246254
},
255+
/**
256+
* [$A1, $A2, ...$AN]
257+
*/
258+
Tuple: (values) => {
259+
return `[${values.join(", ")}]`;
260+
},
247261
};
248262

249263
/**

src/schema-parser/schema-parser.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ class SchemaParser {
9999

100100
const refType = this.schemaUtils.getSchemaRefType(schema);
101101
const $ref = (refType && refType.$ref) || null;
102+
103+
if (Array.isArray(schema.enum) && Array.isArray(schema.enum[0])) {
104+
return this.parseSchema(
105+
{
106+
oneOf: schema.enum.map((enumNames) => ({
107+
type: "array",
108+
items: enumNames.map((enumName) => ({ type: "string", enum: [enumName] })),
109+
})),
110+
},
111+
typeName,
112+
);
113+
}
114+
102115
const keyType = this.getSchemaType(schema);
103116
const enumNames = this.schemaUtils.getEnumNames(schema);
104117
let content = null;
@@ -211,7 +224,7 @@ class SchemaParser {
211224
},
212225
[SCHEMA_TYPES.PRIMITIVE]: (schema, typeName) => {
213226
let contentType = null;
214-
const { additionalProperties, type, description, $$requiredKeys } = schema || {};
227+
const { additionalProperties, type, description, items } = schema || {};
215228

216229
if (type === this.config.Ts.Keyword.Object && additionalProperties) {
217230
const fieldType = _.isObject(additionalProperties)
@@ -227,6 +240,10 @@ class SchemaParser {
227240
});
228241
}
229242

243+
if (_.isArray(items) && type === SCHEMA_TYPES.ARRAY) {
244+
contentType = this.config.Ts.Tuple(items.map((item) => this.getInlineParseContent(item)));
245+
}
246+
230247
return {
231248
...(_.isObject(schema) ? schema : {}),
232249
$parsedSchema: true,
@@ -364,7 +381,7 @@ class SchemaParser {
364381
typeName = this.getSchemaType(schema);
365382
}
366383

367-
if (schema.items && !schema.type) {
384+
if (schema.items && !Array.isArray(schema.items) && !schema.type) {
368385
schema.type = SCHEMA_TYPES.ARRAY;
369386
}
370387
schemaType = this.getInternalSchemaType(schema);

src/schema-parser/schema-routes.js

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class SchemaRoutes {
2828
* @type {SchemaParser}
2929
*/
3030
schemaParser;
31+
/**
32+
* @type {SchemaUtils}
33+
*/
34+
schemaUtils;
3135
/**
3236
* @type {TypeName}
3337
*/
@@ -55,6 +59,7 @@ class SchemaRoutes {
5559
constructor(config, schemaParser, schemaComponentMap, logger, templates, typeName) {
5660
this.config = config;
5761
this.schemaParser = schemaParser;
62+
this.schemaUtils = this.schemaParser.schemaUtils;
5863
this.typeName = typeName;
5964
this.schemaComponentMap = schemaComponentMap;
6065
this.logger = logger;
@@ -470,11 +475,11 @@ class SchemaRoutes {
470475
let typeName = null;
471476

472477
if (this.config.extractRequestBody) {
473-
typeName = this.config.componentTypeNameResolver.resolve([
474-
pascalCase(`${routeName.usage} Payload`),
475-
pascalCase(`${routeName.usage} Body`),
476-
pascalCase(`${routeName.usage} Input`),
477-
]);
478+
typeName = this.schemaUtils.resolveTypeName(
479+
routeName.usage,
480+
this.config.extractingOptions.requestBodySuffix,
481+
this.config.extractingOptions.requestBodyNameResolver,
482+
);
478483
}
479484

480485
if (routeParams.formData.length) {
@@ -571,7 +576,11 @@ class SchemaRoutes {
571576
if (fixedSchema) return fixedSchema;
572577

573578
if (extractRequestParams) {
574-
const typeName = this.config.componentTypeNameResolver.resolve([pascalCase(`${routeName.usage} Params`)]);
579+
const typeName = this.schemaUtils.resolveTypeName(
580+
routeName.usage,
581+
this.config.extractingOptions.requestParamsSuffix,
582+
this.config.extractingOptions.requestParamsNameResolver,
583+
);
575584

576585
return this.schemaComponentMap.createComponent("schemas", typeName, { ...schema });
577586
}
@@ -581,11 +590,11 @@ class SchemaRoutes {
581590

582591
extractResponseBodyIfItNeeded = (routeInfo, responseBodyInfo, routeName) => {
583592
if (responseBodyInfo.responses.length && responseBodyInfo.success && responseBodyInfo.success.schema) {
584-
const typeName = this.config.componentTypeNameResolver.resolve([
585-
pascalCase(`${routeName.usage} Data`),
586-
pascalCase(`${routeName.usage} Result`),
587-
pascalCase(`${routeName.usage} Output`),
588-
]);
593+
const typeName = this.schemaUtils.resolveTypeName(
594+
routeName.usage,
595+
this.config.extractingOptions.responseBodySuffix,
596+
this.config.extractingOptions.responseBodyNameResolver,
597+
);
589598

590599
const idx = responseBodyInfo.responses.indexOf(responseBodyInfo.success.schema);
591600

@@ -608,14 +617,11 @@ class SchemaRoutes {
608617

609618
extractResponseErrorIfItNeeded = (routeInfo, responseBodyInfo, routeName) => {
610619
if (responseBodyInfo.responses.length && responseBodyInfo.error.schemas && responseBodyInfo.error.schemas.length) {
611-
const typeName = this.config.componentTypeNameResolver.resolve([
612-
pascalCase(`${routeName.usage} Error`),
613-
pascalCase(`${routeName.usage} Fail`),
614-
pascalCase(`${routeName.usage} Fails`),
615-
pascalCase(`${routeName.usage} ErrorData`),
616-
pascalCase(`${routeName.usage} HttpError`),
617-
pascalCase(`${routeName.usage} BadResponse`),
618-
]);
620+
const typeName = this.schemaUtils.resolveTypeName(
621+
routeName.usage,
622+
this.config.extractingOptions.responseErrorSuffix,
623+
this.config.extractingOptions.responseErrorNameResolver,
624+
);
619625

620626
const errorSchemas = responseBodyInfo.error.schemas.map(this.getSchemaFromRequestType).filter(Boolean);
621627

@@ -765,7 +771,7 @@ class SchemaRoutes {
765771
const pathType = routeParams.path.length ? this.schemaParser.getInlineParseContent(pathObjectSchema) : null;
766772
const headersType = routeParams.header.length ? this.schemaParser.getInlineParseContent(headersObjectSchema) : null;
767773

768-
const nameResolver = new SpecificArgNameResolver(pathArgsNames);
774+
const nameResolver = new SpecificArgNameResolver(this.logger, pathArgsNames);
769775

770776
const specificArgs = {
771777
query: queryType

src/schema-parser/schema-utils.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const _ = require("lodash");
22
const { SCHEMA_TYPES } = require("../constants");
33
const { internalCase } = require("../util/internal-case");
4+
const { pascalCase } = require("../util/pascal-case");
45

56
class SchemaUtils {
67
/**
@@ -143,6 +144,20 @@ class SchemaUtils {
143144
filterSchemaContents = (contents, filterFn) => {
144145
return _.uniq(_.filter(contents, (type) => filterFn(type)));
145146
};
147+
148+
resolveTypeName = (typeName, suffixes, resolver) => {
149+
if (resolver) {
150+
return this.config.componentTypeNameResolver.resolve((reserved) => {
151+
const variant = resolver(pascalCase(typeName), reserved);
152+
if (variant == null) return variant;
153+
return pascalCase(variant);
154+
});
155+
} else {
156+
return this.config.componentTypeNameResolver.resolve(
157+
suffixes.map((suffix) => pascalCase(`${typeName} ${suffix}`)),
158+
);
159+
}
160+
};
146161
}
147162

148163
module.exports = {

src/templates.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ class Templates {
164164
{
165165
async: false,
166166
...(options || {}),
167-
includeFile: (path, payload, options) => {
168-
return this.renderTemplate(this.getTemplateContent(path), payload, options);
167+
includeFile: (path, configuration, options) => {
168+
return this.renderTemplate(this.getTemplateContent(path), configuration, options);
169169
},
170170
},
171171
);

0 commit comments

Comments
 (0)