swagger-typescript-api 10.0.2 → 11.0.0--alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +263 -41
  2. package/index.d.ts +97 -0
  3. package/index.js +242 -115
  4. package/package.json +121 -116
  5. package/src/code-formatter.js +101 -0
  6. package/src/code-gen-process.js +456 -0
  7. package/src/configuration.js +425 -0
  8. package/src/constants.js +14 -31
  9. package/src/index.js +20 -271
  10. package/src/schema-components-map.js +60 -0
  11. package/src/schema-parser/schema-formatters.js +145 -0
  12. package/src/schema-parser/schema-parser.js +497 -0
  13. package/src/schema-parser/schema-routes.js +902 -0
  14. package/src/swagger-schema-resolver.js +187 -0
  15. package/src/templates.js +174 -155
  16. package/src/translators/JavaScript.js +3 -14
  17. package/src/type-name.js +79 -0
  18. package/src/util/file-system.js +76 -0
  19. package/src/{utils → util}/id.js +9 -9
  20. package/src/util/internal-case.js +5 -0
  21. package/src/util/logger.js +100 -0
  22. package/src/{utils/resolveName.js → util/name-resolver.js} +94 -97
  23. package/src/util/object-assign.js +11 -0
  24. package/src/util/pascal-case.js +5 -0
  25. package/src/{utils → util}/random.js +14 -14
  26. package/templates/base/data-contract-jsdoc.ejs +29 -24
  27. package/templates/base/data-contracts.ejs +3 -3
  28. package/templates/base/interface-data-contract.ejs +1 -0
  29. package/templates/base/route-docs.ejs +3 -4
  30. package/templates/base/route-type.ejs +2 -2
  31. package/templates/default/procedure-call.ejs +2 -2
  32. package/templates/default/route-types.ejs +2 -2
  33. package/templates/modular/api.ejs +2 -2
  34. package/templates/modular/procedure-call.ejs +2 -2
  35. package/templates/modular/route-types.ejs +2 -2
  36. package/src/apiConfig.js +0 -30
  37. package/src/common.js +0 -28
  38. package/src/components.js +0 -91
  39. package/src/config.js +0 -106
  40. package/src/filePrefix.js +0 -14
  41. package/src/files.js +0 -56
  42. package/src/formatFileContent.js +0 -81
  43. package/src/logger.js +0 -59
  44. package/src/modelNames.js +0 -78
  45. package/src/modelTypes.js +0 -31
  46. package/src/output.js +0 -165
  47. package/src/prettierOptions.js +0 -23
  48. package/src/render/utils/fmtToJSDocLine.js +0 -10
  49. package/src/render/utils/index.js +0 -31
  50. package/src/render/utils/templateRequire.js +0 -17
  51. package/src/routeNames.js +0 -46
  52. package/src/routes.js +0 -809
  53. package/src/schema.js +0 -474
  54. package/src/swagger.js +0 -152
  55. package/src/typeFormatters.js +0 -121
@@ -0,0 +1,497 @@
1
+ const { SCHEMA_TYPES } = require("../constants.js");
2
+ const _ = require("lodash");
3
+ const { SchemaFormatters } = require("./schema-formatters");
4
+ const { internalCase } = require("../util/internal-case");
5
+
6
+ class SchemaParser {
7
+ /**
8
+ * @type {Configuration}
9
+ */
10
+ config;
11
+
12
+ /**
13
+ * @type {SchemaComponentsMap}
14
+ */
15
+ schemaComponentsMap;
16
+ /**
17
+ * @type {TypeName}
18
+ */
19
+ typeName;
20
+ /**
21
+ * @type {SchemaFormatters}
22
+ */
23
+ schemaFormatters;
24
+
25
+ constructor(config, logger, templates, schemaComponentsMap, typeName) {
26
+ this.config = config;
27
+ this.schemaComponentsMap = schemaComponentsMap;
28
+ this.typeName = typeName;
29
+ this.schemaFormatters = new SchemaFormatters(config, logger, this, templates);
30
+ }
31
+
32
+ complexSchemaParsers = {
33
+ // T1 | T2
34
+ [SCHEMA_TYPES.COMPLEX_ONE_OF]: (schema) => {
35
+ const combined = _.map(schema.oneOf, (childSchema) =>
36
+ this.getInlineParseContent(this.makeAddRequiredToChildSchema(schema, childSchema)),
37
+ );
38
+ const filtered = this.filterContents(combined, [this.config.Ts.Keyword.Any]);
39
+
40
+ const type = this.config.Ts.UnionType(filtered);
41
+
42
+ return this.checkAndAddNull(schema, type);
43
+ },
44
+ // T1 & T2
45
+ [SCHEMA_TYPES.COMPLEX_ALL_OF]: (schema) => {
46
+ const combined = _.map(schema.allOf, (childSchema) =>
47
+ this.getInlineParseContent(this.makeAddRequiredToChildSchema(schema, childSchema)),
48
+ );
49
+ const filtered = this.filterContents(combined, [...this.config.jsPrimitiveTypes, this.config.Ts.Keyword.Any]);
50
+
51
+ const type = this.config.Ts.IntersectionType(filtered);
52
+
53
+ return this.checkAndAddNull(schema, type);
54
+ },
55
+ // T1 | T2 | (T1 & T2)
56
+ [SCHEMA_TYPES.COMPLEX_ANY_OF]: (schema) => {
57
+ const combined = _.map(schema.anyOf, (childSchema) =>
58
+ this.getInlineParseContent(this.makeAddRequiredToChildSchema(schema, childSchema)),
59
+ );
60
+ const filtered = this.filterContents(combined, [...this.config.jsPrimitiveTypes, this.config.Ts.Keyword.Any]);
61
+
62
+ const type = this.config.Ts.UnionType(
63
+ _.compact([
64
+ ...filtered,
65
+ filtered.length > 1 && this.config.Ts.ExpressionGroup(this.config.Ts.IntersectionType(filtered)),
66
+ ]),
67
+ );
68
+
69
+ return this.checkAndAddNull(schema, type);
70
+ },
71
+ // TODO
72
+ [SCHEMA_TYPES.COMPLEX_NOT]: (schema) => {
73
+ // TODO
74
+ return this.config.Ts.Keyword.Any;
75
+ },
76
+ };
77
+
78
+ baseSchemaParsers = {
79
+ [SCHEMA_TYPES.ENUM]: (schema, typeName) => {
80
+ const refType = this.getRefType(schema);
81
+ const $ref = (refType && refType.$ref) || null;
82
+ const enumNamesAsValues = this.config.enumNamesAsValues;
83
+ const keyType = this.getType(schema);
84
+ const enumNames = this.getEnumNames(schema);
85
+ const isIntegerOrBooleanEnum =
86
+ keyType === this.getTypeAlias({ type: "number" }) || keyType === this.getTypeAlias({ type: "boolean" });
87
+ let content = null;
88
+
89
+ const formatValue = (value) => {
90
+ if (value === null) {
91
+ return this.config.Ts.NullValue(value);
92
+ }
93
+ if (keyType === this.getTypeAlias({ type: "number" })) {
94
+ return this.config.Ts.NumberValue(value);
95
+ }
96
+ if (keyType === this.getTypeAlias({ type: "boolean" })) {
97
+ return this.config.Ts.BooleanValue(value);
98
+ }
99
+
100
+ return this.config.Ts.StringValue(value);
101
+ };
102
+
103
+ if (_.isArray(enumNames) && _.size(enumNames)) {
104
+ content = _.map(enumNames, (enumName, index) => {
105
+ const enumValue = _.get(schema.enum, index);
106
+ const formattedKey =
107
+ (enumName && this.typeName.format(enumName, { ignorePrefix: true, ignoreSuffix: true })) ||
108
+ this.typeName.format(enumValue, { ignorePrefix: true, ignoreSuffix: true });
109
+
110
+ if (enumNamesAsValues || _.isUndefined(enumValue)) {
111
+ return {
112
+ key: formattedKey,
113
+ type: this.config.Ts.Keyword.String,
114
+ value: this.config.Ts.StringValue(enumName),
115
+ };
116
+ }
117
+
118
+ return {
119
+ key: formattedKey,
120
+ type: keyType,
121
+ value: formatValue(enumValue),
122
+ };
123
+ });
124
+ } else {
125
+ content = _.map(schema.enum, (key) => {
126
+ return {
127
+ key: isIntegerOrBooleanEnum ? key : this.typeName.format(key, { ignorePrefix: true, ignoreSuffix: true }),
128
+ type: keyType,
129
+ value: formatValue(key),
130
+ };
131
+ });
132
+ }
133
+
134
+ return this.attachParsedRef(schema, {
135
+ ...(_.isObject(schema) ? schema : {}),
136
+ $ref: $ref,
137
+ typeName: typeName || ($ref && refType.typeName) || null,
138
+ $parsedSchema: true,
139
+ schemaType: SCHEMA_TYPES.ENUM,
140
+ type: SCHEMA_TYPES.ENUM,
141
+ keyType: keyType,
142
+ typeIdentifier:
143
+ this.config.generateUnionEnums || (!enumNames && isIntegerOrBooleanEnum)
144
+ ? this.config.Ts.Keyword.Type
145
+ : this.config.Ts.Keyword.Enum,
146
+ name: typeName,
147
+ description: this.schemaFormatters.formatDescription(schema.description),
148
+ content,
149
+ });
150
+ },
151
+ [SCHEMA_TYPES.OBJECT]: (schema, typeName) => {
152
+ const content = this.getObjectSchemaContent(schema);
153
+
154
+ return this.attachParsedRef(schema, {
155
+ ...(_.isObject(schema) ? schema : {}),
156
+ $parsedSchema: true,
157
+ schemaType: SCHEMA_TYPES.OBJECT,
158
+ type: SCHEMA_TYPES.OBJECT,
159
+ typeIdentifier: this.config.Ts.Keyword.Interface,
160
+ name: typeName,
161
+ description: this.schemaFormatters.formatDescription(schema.description),
162
+ allFieldsAreOptional: !_.some(_.values(content), (part) => part.isRequired),
163
+ content: content,
164
+ });
165
+ },
166
+ [SCHEMA_TYPES.COMPLEX]: (schema, typeName) => {
167
+ const complexType = this.getComplexType(schema);
168
+ const simpleSchema = _.omit(_.clone(schema), _.keys(this.complexSchemaParsers));
169
+ const complexSchemaContent = this.complexSchemaParsers[complexType](schema);
170
+
171
+ return this.attachParsedRef(schema, {
172
+ ...(_.isObject(schema) ? schema : {}),
173
+ $parsedSchema: true,
174
+ schemaType: SCHEMA_TYPES.COMPLEX,
175
+ type: SCHEMA_TYPES.PRIMITIVE,
176
+ typeIdentifier: this.config.Ts.Keyword.Type,
177
+ name: typeName,
178
+ description: this.schemaFormatters.formatDescription(
179
+ schema.description || _.compact(_.map(schema[complexType], "description"))[0] || "",
180
+ ),
181
+ content:
182
+ this.config.Ts.IntersectionType(
183
+ _.compact([
184
+ this.config.Ts.ExpressionGroup(complexSchemaContent),
185
+ this.getInternalSchemaType(simpleSchema) === SCHEMA_TYPES.OBJECT &&
186
+ this.config.Ts.ExpressionGroup(this.getInlineParseContent(simpleSchema)),
187
+ ]),
188
+ ) || this.config.Ts.Keyword.Any,
189
+ });
190
+ },
191
+ [SCHEMA_TYPES.PRIMITIVE]: (schema, typeName) => {
192
+ let contentType = null;
193
+ const { additionalProperties, type, description, $$requiredKeys } = schema || {};
194
+
195
+ if (type === this.config.Ts.Keyword.Object && additionalProperties) {
196
+ const fieldType = _.isObject(additionalProperties)
197
+ ? this.getInlineParseContent(additionalProperties)
198
+ : this.config.Ts.Keyword.Any;
199
+ contentType = this.config.Ts.RecordType(this.config.Ts.Keyword.String, fieldType);
200
+ }
201
+
202
+ if (_.isArray(type) && type.length) {
203
+ contentType = this.complexSchemaParsers.oneOf({
204
+ ...(_.isObject(schema) ? schema : {}),
205
+ oneOf: type.map((type) => ({ type })),
206
+ });
207
+ }
208
+
209
+ return this.attachParsedRef(schema, {
210
+ ...(_.isObject(schema) ? schema : {}),
211
+ $parsedSchema: true,
212
+ schemaType: SCHEMA_TYPES.PRIMITIVE,
213
+ type: SCHEMA_TYPES.PRIMITIVE,
214
+ typeIdentifier: this.config.Ts.Keyword.Type,
215
+ name: typeName,
216
+ description: this.schemaFormatters.formatDescription(description),
217
+ // TODO: probably it should be refactored. `type === 'null'` is not flexible
218
+ content: type === this.config.Ts.Keyword.Null ? type : contentType || this.getType(schema),
219
+ });
220
+ },
221
+ };
222
+
223
+ filterContents = (contents, types) => _.uniq(_.filter(contents, (type) => !_.includes(types, type)));
224
+
225
+ makeAddRequiredToChildSchema = (parentSchema, childSchema) => {
226
+ if (!childSchema) return childSchema;
227
+
228
+ const required = _.uniq([...(parentSchema.required || []), ...(childSchema.required || [])]);
229
+
230
+ const refData = this.getRefType(childSchema);
231
+
232
+ if (refData) {
233
+ const refObjectProperties = _.keys((refData.rawTypeData && refData.rawTypeData.properties) || {});
234
+ const existedRequiredKeys = refObjectProperties.filter((key) => required.includes(key));
235
+
236
+ if (!existedRequiredKeys.length) return childSchema;
237
+
238
+ return {
239
+ ...childSchema,
240
+ $$requiredKeys: existedRequiredKeys,
241
+ };
242
+ } else if (childSchema.properties) {
243
+ const childSchemaProperties = _.keys(childSchema.properties);
244
+ const existedRequiredKeys = childSchemaProperties.filter((key) => required.includes(key));
245
+
246
+ if (!existedRequiredKeys.length) return childSchema;
247
+
248
+ return {
249
+ required: _.uniq([...(childSchema.required || []), ...existedRequiredKeys]),
250
+ ...childSchema,
251
+ };
252
+ }
253
+
254
+ return childSchema;
255
+ };
256
+
257
+ attachParsedRef = (originalSchema, parsedSchema) => {
258
+ const parsedSchemaAfterHook = this.config.hooks.onParseSchema(originalSchema, parsedSchema) || parsedSchema;
259
+
260
+ if (originalSchema) {
261
+ originalSchema.$parsed = parsedSchemaAfterHook;
262
+ }
263
+
264
+ return parsedSchemaAfterHook;
265
+ };
266
+
267
+ stealTypeFromSchema = (rawSchema) => {
268
+ const schema = rawSchema || {};
269
+
270
+ if (schema.type) {
271
+ return schema.type;
272
+ }
273
+ if (schema.enum) {
274
+ const enumFieldType = typeof schema.enum[0];
275
+ if (enumFieldType === this.config.Ts.Keyword.Undefined) return;
276
+
277
+ return enumFieldType;
278
+ }
279
+ if (_.keys(schema.properties).length) {
280
+ return SCHEMA_TYPES.OBJECT;
281
+ }
282
+ if (!!schema.items) {
283
+ return SCHEMA_TYPES.ARRAY;
284
+ }
285
+ };
286
+
287
+ getTypeAlias = (rawSchema) => {
288
+ const schema = rawSchema || {};
289
+ const type = internalCase(this.stealTypeFromSchema(schema));
290
+ const typeAlias =
291
+ _.get(this.config.primitiveTypes, [type, schema.format]) ||
292
+ _.get(this.config.primitiveTypes, [type, "$default"]) ||
293
+ this.config.primitiveTypes[type];
294
+
295
+ if (_.isFunction(typeAlias)) {
296
+ return typeAlias(schema, this);
297
+ }
298
+
299
+ return typeAlias || type;
300
+ };
301
+
302
+ getEnumNames = (schema) => {
303
+ return schema["x-enumNames"] || schema["xEnumNames"] || schema["x-enumnames"] || schema["x-enum-varnames"];
304
+ };
305
+
306
+ getInternalSchemaType = (schema) => {
307
+ if (!_.isEmpty(schema.enum) || !_.isEmpty(this.getEnumNames(schema))) return SCHEMA_TYPES.ENUM;
308
+ if (schema.allOf || schema.oneOf || schema.anyOf || schema.not) return SCHEMA_TYPES.COMPLEX;
309
+ if (!_.isEmpty(schema.properties)) return SCHEMA_TYPES.OBJECT;
310
+
311
+ return SCHEMA_TYPES.PRIMITIVE;
312
+ };
313
+
314
+ isNeedToAddNull = (contract, value) => {
315
+ const { nullable, type } = contract || {};
316
+ return (
317
+ (nullable || !!_.get(contract, "x-nullable") || type === this.config.Ts.Keyword.Null) &&
318
+ (!_.isString(value) ||
319
+ (!value.includes(` ${this.config.Ts.Keyword.Null}`) && !value.includes(`${this.config.Ts.Keyword.Null} `)))
320
+ );
321
+ };
322
+
323
+ checkAndAddRequiredKeys = (schema, resultType) => {
324
+ if ("$$requiredKeys" in schema && schema.$$requiredKeys.length) {
325
+ this.config.update({
326
+ internalTemplateOptions: {
327
+ addUtilRequiredKeysType: true,
328
+ },
329
+ });
330
+ return this.config.Ts.TypeWithGeneric(this.config.Ts.CodeGenKeyword.UtilRequiredKeys, [
331
+ resultType,
332
+ this.config.Ts.UnionType(schema.$$requiredKeys.map(this.config.Ts.StringValue)),
333
+ ]);
334
+ }
335
+
336
+ return resultType;
337
+ };
338
+
339
+ checkAndAddNull = (schema, value) => {
340
+ const { nullable, type } = schema || {};
341
+ return (nullable || !!_.get(schema, "x-nullable") || type === this.config.Ts.Keyword.Null) &&
342
+ _.isString(value) &&
343
+ !value.includes(` ${this.config.Ts.Keyword.Null}`) &&
344
+ !value.includes(`${this.config.Ts.Keyword.Null} `)
345
+ ? this.config.Ts.UnionType([value, this.config.Ts.Keyword.Null])
346
+ : value;
347
+ };
348
+
349
+ isRef = (property) => {
350
+ return !!(property && property["$ref"]);
351
+ };
352
+
353
+ getRefType = (schema) => {
354
+ const ref = schema && schema["$ref"];
355
+ return this.schemaComponentsMap.get(ref);
356
+ };
357
+
358
+ getType = (schema) => {
359
+ if (!schema) return this.config.Ts.Keyword.Any;
360
+
361
+ const refTypeInfo = this.getRefType(schema);
362
+
363
+ if (refTypeInfo) {
364
+ return this.checkAndAddRequiredKeys(
365
+ schema,
366
+ this.checkAndAddNull(schema, this.typeName.format(refTypeInfo.typeName)),
367
+ );
368
+ }
369
+
370
+ const primitiveType = this.getTypeAlias(schema);
371
+ return primitiveType
372
+ ? this.checkAndAddRequiredKeys(schema, this.checkAndAddNull(schema, primitiveType))
373
+ : this.config.Ts.Keyword.Any;
374
+ };
375
+
376
+ isRequired = (property, name, requiredProperties) => {
377
+ if (property["x-omitempty"] === false) {
378
+ return true;
379
+ }
380
+
381
+ const isRequired = _.isBoolean(property.required)
382
+ ? !!property.required
383
+ : _.isArray(requiredProperties)
384
+ ? requiredProperties.includes(name)
385
+ : !!requiredProperties;
386
+
387
+ if (this.config.convertedFromSwagger2) {
388
+ return typeof property.nullable === this.config.Ts.Keyword.Undefined ? isRequired : !property.nullable;
389
+ }
390
+ return isRequired;
391
+ };
392
+
393
+ getObjectSchemaContent = (schema) => {
394
+ const { properties, additionalProperties, required: requiredProperties } = schema || {};
395
+
396
+ const propertiesContent = _.map(properties, (property, name) => {
397
+ const required = this.isRequired(property, name, requiredProperties);
398
+ const rawTypeData = _.get(this.getRefType(property), "rawTypeData", {});
399
+ const nullable = !!(rawTypeData.nullable || property.nullable);
400
+ const fieldName = this.typeName.isValidName(name) ? name : this.config.Ts.StringValue(name);
401
+ const fieldValue = this.getInlineParseContent(property);
402
+ const readOnly = property.readOnly;
403
+
404
+ return {
405
+ ...property,
406
+ $$raw: property,
407
+ title: property.title,
408
+ description:
409
+ property.description ||
410
+ _.compact(_.map(property[this.getComplexType(property)], "description"))[0] ||
411
+ rawTypeData.description ||
412
+ _.compact(_.map(rawTypeData[this.getComplexType(rawTypeData)], "description"))[0] ||
413
+ "",
414
+ isRequired: required,
415
+ isNullable: nullable,
416
+ name: fieldName,
417
+ value: fieldValue,
418
+ field: this.config.Ts.TypeField({
419
+ readonly: readOnly && this.config.addReadonly,
420
+ optional: !required,
421
+ key: fieldName,
422
+ value: fieldValue,
423
+ }),
424
+ };
425
+ });
426
+
427
+ if (additionalProperties === true) {
428
+ propertiesContent.push({
429
+ $$raw: { additionalProperties },
430
+ description: "",
431
+ isRequired: false,
432
+ field: this.config.Ts.InterfaceDynamicField(this.config.Ts.Keyword.String, this.config.Ts.Keyword.Any),
433
+ });
434
+ }
435
+
436
+ return propertiesContent;
437
+ };
438
+
439
+ getComplexType = (schema) => {
440
+ if (schema.oneOf) return SCHEMA_TYPES.COMPLEX_ONE_OF;
441
+ if (schema.allOf) return SCHEMA_TYPES.COMPLEX_ALL_OF;
442
+ if (schema.anyOf) return SCHEMA_TYPES.COMPLEX_ANY_OF;
443
+ // TODO :(
444
+ if (schema.not) return SCHEMA_TYPES.COMPLEX_NOT;
445
+
446
+ return SCHEMA_TYPES.COMPLEX_UNKNOWN;
447
+ };
448
+
449
+ /**
450
+ *
451
+ * @param schema {any}
452
+ * @param typeName {null | string}
453
+ * @param formatter {"inline" | "base"}
454
+ * @return {Record<string, any>}
455
+ */
456
+ parseSchema = (schema, typeName, formatter) => {
457
+ if (!schema) return this.baseSchemaParsers[SCHEMA_TYPES.PRIMITIVE](null, typeName);
458
+
459
+ let schemaType = null;
460
+ let parsedSchema = null;
461
+
462
+ if (typeof schema === "string") {
463
+ return schema;
464
+ }
465
+
466
+ if (schema.$parsed) {
467
+ schemaType = schema.$parsed.schemaType;
468
+ parsedSchema = schema.$parsed;
469
+ } else {
470
+ if (!typeName && this.isRef(schema)) {
471
+ typeName = this.getType(schema);
472
+ }
473
+
474
+ if (schema.items && !schema.type) {
475
+ schema.type = SCHEMA_TYPES.ARRAY;
476
+ }
477
+
478
+ schemaType = this.getInternalSchemaType(schema);
479
+ parsedSchema = this.baseSchemaParsers[schemaType](schema, typeName);
480
+ }
481
+
482
+ const formatterFn =
483
+ formatter in this.schemaFormatters &&
484
+ this.schemaFormatters[formatter] &&
485
+ this.schemaFormatters[formatter][schemaType];
486
+
487
+ return (formatterFn && formatterFn(parsedSchema)) || parsedSchema;
488
+ };
489
+
490
+ getInlineParseContent = (rawTypeData, typeName = null) => this.parseSchema(rawTypeData, typeName, "inline").content;
491
+
492
+ getParseContent = (rawTypeData, typeName = null) => this.parseSchema(rawTypeData, typeName).content;
493
+ }
494
+
495
+ module.exports = {
496
+ SchemaParser,
497
+ };