Skip to content

Commit 1ef9b61

Browse files
committed
feat: new hook onPreParseSchema;
BREAKING_CHANGE: add ability to custom prefix for autofix invalid enum keys, invalid type names with nodejs options (`fixInvalidTypeNamePrefix: string`, `fixInvalidEnumKeyPrefix: string`); (#344) BREAKING_CHANGE: by default all component enum schemas (even numeric) extracting as `enum` TS constructions; (#344) feature: ability to extract all enums from nested types\interfaces to `enum` TS construction using `--extract-enums` option (#344)
1 parent 02658ce commit 1ef9b61

File tree

28 files changed

+629
-93
lines changed

28 files changed

+629
-93
lines changed

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ onPreBuildRoutePath: (routePath: string) => string | void;
88
onBuildRoutePath: (data: BuildRoutePath) => BuildRoutePath | void;
99
/** calls before insert path param name into string path interpolation */
1010
onInsertPathParam: (paramName: string, index: number, arr: BuildRouteParam[], resultRoute: string) => string | void;
11-
```
11+
/** calls before parse any kind of schema */
12+
onPreParseSchema: (originalSchema: any, typeName: string, schemaType: string) => any;
13+
```
14+
BREAKING_CHANGE: add ability to custom prefix for autofix invalid enum keys, invalid type names with nodejs options (`fixInvalidTypeNamePrefix: string`, `fixInvalidEnumKeyPrefix: string`)
15+
BREAKING_CHANGE: by default all component enum schemas (even numeric) extracting as `enum` TS constructions (#344)
16+
feature: ability to extract all enums from nested types\interfaces to `enum` TS construction using `--extract-enums` option (#344)
1217
feature: ability to modify route path params before insert them into string (request url, #446, with using hook `onInsertPathParam`)
1318
docs: update docs for `extraTemplates` option
1419

@@ -468,7 +473,7 @@ Features:
468473
- `--type-suffix` option. Allows to set suffix for data contract name. (issue #191, thanks @the-ult)
469474
- `--type-prefix` option. Allows to set prefix for data contract name. (issue #191, thanks @the-ult)
470475
Examples [here](./spec/typeSuffixPrefix/schema.ts)
471-
- `onFormatTypeName(usageTypeName, rawTypeName)` hook. Allow to format data contract names as you want.
476+
- `onFormatTypeName(usageTypeName, rawTypeName, schemaType)` hook. Allow to format data contract names as you want.
472477

473478
Internal:
474479
- rename and split `checkAndRenameModelName` -> `formatModelName`, `fixModelName`

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ Options:
7070
--type-prefix <string> data contract name prefix (default: "")
7171
--type-suffix <string> data contract name suffix (default: "")
7272
--clean-output clean output folder before generate api. WARNING: May cause data loss (default: false)
73-
--api-class-name <string> name of the api class
73+
--api-class-name <string> name of the api class (default: "Api")
7474
--patch fix up small errors in the swagger source definition (default: false)
7575
--debug additional information about processes inside this tool (default: false)
7676
--another-array-type generate array types as Array<Type> (by default Type[]) (default: false)
7777
--sort-types sort fields and types (default: false)
78+
--extract-enums extract all enums from inline interface\type content to typescript enum construction (default: false)
7879
-h, --help display help for command
7980
8081
Commands:
@@ -141,7 +142,9 @@ generateApi({
141142
addReadonly: false,
142143
/** allow to generate extra files based with this extra templates, see more below */
143144
extraTemplates: [],
144-
anotherArrayType: false,
145+
anotherArrayType: false,
146+
fixInvalidTypeNamePrefix: "Type",
147+
fixInvalidEnumKeyPrefix: "Value",
145148
codeGenConstructs: (constructs) => ({
146149
...constructs,
147150
RecordType: (key, value) => `MyRecord<key, value>`
@@ -158,8 +161,9 @@ generateApi({
158161
onCreateRoute: (routeData) => {},
159162
onCreateRouteName: (routeNameInfo, rawRouteInfo) => {},
160163
onFormatRouteName: (routeInfo, templateRouteName) => {},
161-
onFormatTypeName: (typeName, rawTypeName) => {},
164+
onFormatTypeName: (typeName, rawTypeName, schemaType) => {},
162165
onInit: (configuration) => {},
166+
onPreParseSchema: (originalSchema, typeName, schemaType) => {},
163167
onParseSchema: (originalSchema, parsedSchema) => {},
164168
onPrepareConfig: (currentConfiguration) => {},
165169
}

index.d.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ interface GenerateApiParamsBase {
136136
primitiveTypeConstructs?: (struct: PrimitiveTypeStruct) => Partial<PrimitiveTypeStruct>;
137137

138138
codeGenConstructs?: (struct: CodeGenConstruct) => Partial<CodeGenConstruct>;
139+
140+
/** extract all enums from nested types\interfaces to `enum` construction */
141+
extractEnums?: boolean;
142+
/** prefix string value needed to fix invalid type names (default: 'Type') */
143+
fixInvalidTypeNamePrefix?: string;
144+
/** prefix string value needed to fix invalid enum keys (default: 'Value') */
145+
fixInvalidEnumKeyPrefix?: string;
139146
}
140147

141148
type CodeGenConstruct = {
@@ -243,6 +250,8 @@ export interface Hooks {
243250
onInsertPathParam: (paramName: string, index: number, arr: BuildRouteParam[], resultRoute: string) => string | void;
244251
/** calls after parse schema component */
245252
onCreateComponent: (component: SchemaComponent) => SchemaComponent | void;
253+
/** calls before parse any kind of schema */
254+
onPreParseSchema: (originalSchema: any, typeName: string, schemaType: string) => any;
246255
/** calls after parse any kind of schema */
247256
onParseSchema: (originalSchema: any, parsedSchema: any) => any | void;
248257
/** calls after parse route (return type: customized route (ParsedRoute), nothing change (void), false (ignore this route)) */
@@ -256,7 +265,7 @@ export interface Hooks {
256265
/** customize request params (path params, query params) */
257266
onCreateRequestParams?: (rawType: SchemaComponent["rawTypeData"]) => SchemaComponent["rawTypeData"] | void;
258267
/** customize name of model type */
259-
onFormatTypeName?: (typeName: string, rawTypeName?: string) => string | void;
268+
onFormatTypeName?: (typeName: string, rawTypeName?: string, schemaType?: "type-name" | "enum-key") => string | void;
260269
/** customize name of route (operationId), you can do it with using onCreateRouteName too */
261270
onFormatRouteName?: (routeInfo: RawRouteInfo, templateRouteName: string) => string | void;
262271
}
@@ -448,6 +457,9 @@ export interface GenerateApiConfiguration {
448457
addReadonly: boolean;
449458
extractResponseBody: boolean;
450459
extractResponseError: boolean;
460+
extractEnums: boolean;
461+
fixInvalidTypeNamePrefix: string;
462+
fixInvalidEnumKeyPrefix: string;
451463
defaultResponseType: boolean;
452464
toJS: boolean;
453465
disableThrowOnError: boolean;

index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ const program = cli({
202202
description: "sort fields and types",
203203
default: codeGenBaseConfig.sortTypes,
204204
},
205+
{
206+
flags: "--extract-enums",
207+
description: "extract all enums from inline interface\\type content to typescript enum construction",
208+
default: codeGenBaseConfig.extractEnums,
209+
},
205210
],
206211
});
207212

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
"test:enums2.0": "node tests/spec/enums-2.0/test.js",
6060
"test:another-query-params": "node tests/spec/another-query-params/test.js",
6161
"test:on-insert-path-param": "node tests/spec/on-insert-path-param/test.js",
62-
"test:extra-templates": "node tests/spec/extra-templates/test.js"
62+
"test:extra-templates": "node tests/spec/extra-templates/test.js",
63+
"test:extract-enums": "node tests/spec/extract-enums/test.js"
6364
},
6465
"author": "acacode",
6566
"license": "MIT",

src/configuration.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class CodeGenConfig {
7272
extractRequestBody = false;
7373
extractResponseBody = false;
7474
extractResponseError = false;
75+
extractEnums = false;
7576
fileNames = {
7677
dataContracts: "data-contracts",
7778
routeTypes: "route-types",
@@ -85,13 +86,14 @@ class CodeGenConfig {
8586
onBuildRoutePath: (routeData) => void 0,
8687
onInsertPathParam: (pathParam) => void 0,
8788
onCreateComponent: (schema) => schema,
89+
onPreParseSchema: (originalSchema, typeName, schemaType) => void 0,
8890
onParseSchema: (originalSchema, parsedSchema) => parsedSchema,
8991
onCreateRoute: (routeData) => routeData,
9092
onInit: (config) => config,
9193
onPrepareConfig: (apiConfig) => apiConfig,
9294
onCreateRequestParams: (rawType) => {},
9395
onCreateRouteName: () => {},
94-
onFormatTypeName: (typeName, rawTypeName) => {},
96+
onFormatTypeName: (typeName, rawTypeName, schemaType) => {},
9597
onFormatRouteName: (routeInfo, templateRouteName) => {},
9698
};
9799
defaultResponseType;
@@ -151,6 +153,8 @@ class CodeGenConfig {
151153

152154
jsPrimitiveTypes = [];
153155
jsEmptyTypes = [];
156+
fixInvalidTypeNamePrefix = "Type";
157+
fixInvalidEnumKeyPrefix = "Value";
154158

155159
successResponseStatusRange = [200, 299];
156160

src/schema-parser/schema-formatters.js

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ class SchemaFormatters {
2828

2929
base = {
3030
[SCHEMA_TYPES.ENUM]: (parsedSchema) => {
31-
const isNumberEnum = _.some(parsedSchema.content, (content) => typeof content.key === "number");
32-
const formatAsUnionType = !!(isNumberEnum || this.config.generateUnionEnums);
33-
34-
if (formatAsUnionType) {
31+
if (this.config.generateUnionEnums) {
3532
return {
3633
...parsedSchema,
3734
$content: parsedSchema.content,
@@ -61,6 +58,19 @@ class SchemaFormatters {
6158
},
6259
};
6360
inline = {
61+
[SCHEMA_TYPES.ENUM]: (parsedSchema) => {
62+
return {
63+
...parsedSchema,
64+
content: parsedSchema.$ref
65+
? parsedSchema.typeName
66+
: this.config.Ts.UnionType(
67+
_.compact([
68+
..._.map(parsedSchema.content, ({ value }) => `${value}`),
69+
parsedSchema.nullable && this.config.Ts.Keyword.Null,
70+
]),
71+
),
72+
};
73+
},
6474
[SCHEMA_TYPES.OBJECT]: (parsedSchema) => {
6575
if (_.isString(parsedSchema.content)) {
6676
return {
@@ -81,19 +91,6 @@ class SchemaFormatters {
8191
),
8292
};
8393
},
84-
[SCHEMA_TYPES.ENUM]: (parsedSchema) => {
85-
return {
86-
...parsedSchema,
87-
content: parsedSchema.$ref
88-
? parsedSchema.typeName
89-
: this.config.Ts.UnionType(
90-
_.compact([
91-
..._.map(parsedSchema.content, ({ value }) => `${value}`),
92-
parsedSchema.nullable && this.config.Ts.Keyword.Null,
93-
]),
94-
),
95-
};
96-
},
9794
};
9895

9996
/**

src/schema-parser/schema-parser.js

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const _ = require("lodash");
33
const { SchemaFormatters } = require("./schema-formatters");
44
const { internalCase } = require("../util/internal-case");
55
const { SchemaUtils } = require("./schema-utils");
6+
const { camelCase } = require("lodash");
7+
const { pascalCase } = require("../util/pascal-case");
68

79
class SchemaParser {
810
/**
@@ -28,6 +30,8 @@ class SchemaParser {
2830
*/
2931
schemaUtils;
3032

33+
$processingSchemaPath = [];
34+
3135
constructor(config, logger, templates, schemaComponentsMap, typeName) {
3236
this.config = config;
3337
this.schemaComponentsMap = schemaComponentsMap;
@@ -87,13 +91,16 @@ class SchemaParser {
8791

8892
baseSchemaParsers = {
8993
[SCHEMA_TYPES.ENUM]: (schema, typeName) => {
94+
if (this.config.extractEnums && !typeName) {
95+
const generatedTypeName = this.config.componentTypeNameResolver.resolve([this.buildTypeNameFromPath()]);
96+
const schemaComponent = this.schemaComponentsMap.createComponent("schemas", generatedTypeName, { ...schema });
97+
return this.parseSchema(schemaComponent, generatedTypeName);
98+
}
99+
90100
const refType = this.schemaUtils.getSchemaRefType(schema);
91101
const $ref = (refType && refType.$ref) || null;
92-
const enumNamesAsValues = this.config.enumNamesAsValues;
93102
const keyType = this.getSchemaType(schema);
94103
const enumNames = this.schemaUtils.getEnumNames(schema);
95-
const isIntegerOrBooleanEnum =
96-
keyType === this.getSchemaType({ type: "number" }) || keyType === this.getSchemaType({ type: "boolean" });
97104
let content = null;
98105

99106
const formatValue = (value) => {
@@ -114,10 +121,19 @@ class SchemaParser {
114121
content = _.map(enumNames, (enumName, index) => {
115122
const enumValue = _.get(schema.enum, index);
116123
const formattedKey =
117-
(enumName && this.typeName.format(enumName, { ignorePrefix: true, ignoreSuffix: true })) ||
118-
this.typeName.format(enumValue, { ignorePrefix: true, ignoreSuffix: true });
119-
120-
if (enumNamesAsValues || _.isUndefined(enumValue)) {
124+
(enumName &&
125+
this.typeName.format(enumName, {
126+
ignorePrefix: true,
127+
ignoreSuffix: true,
128+
type: "enum-key",
129+
})) ||
130+
this.typeName.format(`${enumValue}`, {
131+
ignorePrefix: true,
132+
ignoreSuffix: true,
133+
type: "enum-key",
134+
});
135+
136+
if (this.config.enumNamesAsValues || _.isUndefined(enumValue)) {
121137
return {
122138
key: formattedKey,
123139
type: this.config.Ts.Keyword.String,
@@ -134,7 +150,11 @@ class SchemaParser {
134150
} else {
135151
content = _.map(schema.enum, (key) => {
136152
return {
137-
key: isIntegerOrBooleanEnum ? key : this.typeName.format(key, { ignorePrefix: true, ignoreSuffix: true }),
153+
key: this.typeName.format(`${key}`, {
154+
ignorePrefix: true,
155+
ignoreSuffix: true,
156+
type: "enum-key",
157+
}),
138158
type: keyType,
139159
value: formatValue(key),
140160
};
@@ -149,10 +169,7 @@ class SchemaParser {
149169
schemaType: SCHEMA_TYPES.ENUM,
150170
type: SCHEMA_TYPES.ENUM,
151171
keyType: keyType,
152-
typeIdentifier:
153-
this.config.generateUnionEnums || (!enumNames && isIntegerOrBooleanEnum)
154-
? this.config.Ts.Keyword.Type
155-
: this.config.Ts.Keyword.Enum,
172+
typeIdentifier: this.config.generateUnionEnums ? this.config.Ts.Keyword.Type : this.config.Ts.Keyword.Enum,
156173
name: typeName,
157174
description: this.schemaFormatters.formatDescription(schema.description),
158175
content,
@@ -276,13 +293,16 @@ class SchemaParser {
276293
const { properties, additionalProperties } = schema || {};
277294

278295
const propertiesContent = _.map(properties, (property, name) => {
296+
this.$processingSchemaPath.push(name);
279297
const required = this.schemaUtils.isPropertyRequired(name, property, schema);
280298
const rawTypeData = _.get(this.schemaUtils.getSchemaRefType(property), "rawTypeData", {});
281299
const nullable = !!(rawTypeData.nullable || property.nullable);
282300
const fieldName = this.typeName.isValidName(name) ? name : this.config.Ts.StringValue(name);
283301
const fieldValue = this.getInlineParseContent(property);
284302
const readOnly = property.readOnly;
285303

304+
this.$processingSchemaPath.pop();
305+
286306
return {
287307
...property,
288308
$$raw: property,
@@ -353,12 +373,17 @@ class SchemaParser {
353373
if (schema.items && !schema.type) {
354374
schema.type = SCHEMA_TYPES.ARRAY;
355375
}
356-
357376
schemaType = this.getInternalSchemaType(schema);
377+
378+
this.$processingSchemaPath.push(typeName);
379+
380+
_.merge(schema, this.config.hooks.onPreParseSchema(schema, typeName, schemaType));
358381
parsedSchema = this.baseSchemaParsers[schemaType](schema, typeName);
359382
schema.$parsed = this.config.hooks.onParseSchema(schema, parsedSchema) || parsedSchema;
360383
}
361384

385+
this.$processingSchemaPath.pop();
386+
362387
return schema.$parsed;
363388
};
364389

@@ -373,6 +398,14 @@ class SchemaParser {
373398
const formattedSchema = this.schemaFormatters.formatSchema(parsedSchema, "base");
374399
return formattedSchema.content;
375400
};
401+
402+
buildTypeNameFromPath = () => {
403+
const schemaPath = _.uniq(_.compact(this.$processingSchemaPath));
404+
405+
if (!schemaPath || !schemaPath[0]) return null;
406+
407+
return internalCase(camelCase(`${schemaPath[0]}_${schemaPath[schemaPath.length - 1]}`));
408+
};
376409
}
377410

378411
module.exports = {

0 commit comments

Comments
 (0)