ts-class-to-openapi 1.2.1 → 1.2.3

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.
package/README.md CHANGED
@@ -231,7 +231,7 @@ const schema = transform(User)
231
231
 
232
232
  ### 4. Enumerations and Special Types
233
233
 
234
- Full compatibility with TypeScript enumerations:
234
+ Full compatibility with TypeScript enumerations (both decorated and pure):
235
235
 
236
236
  ```typescript
237
237
  import { transform } from 'ts-class-to-openapi'
@@ -253,6 +253,9 @@ class Task {
253
253
  @IsEnum(UserType)
254
254
  assignedTo: UserType
255
255
 
256
+ // Pure TypeScript enum (automatically detected without decorator)
257
+ status: UserType
258
+
256
259
  @IsEnum(Priority)
257
260
  priority?: Priority
258
261
 
@@ -276,6 +279,10 @@ const schema = transform(Task)
276
279
  "type": "string",
277
280
  "enum": ["admin", "user", "moderator"]
278
281
  },
282
+ "status": {
283
+ "type": "string",
284
+ "enum": ["admin", "user", "moderator"]
285
+ },
279
286
  "priority": {
280
287
  "type": "number",
281
288
  "enum": [1, 2, 3]
@@ -287,7 +294,7 @@ const schema = transform(Task)
287
294
  "format": "date-time"
288
295
  }
289
296
  },
290
- "required": ["assignedTo", "title", "completed", "dueDate"]
297
+ "required": ["assignedTo", "status", "title", "completed", "dueDate"]
291
298
  }
292
299
  }
293
300
  ```
@@ -0,0 +1,3 @@
1
+ export declare class ArrayCollision {
2
+ tags: number[];
3
+ }
@@ -0,0 +1,3 @@
1
+ export declare class ArrayCollision {
2
+ tags: string[];
3
+ }
@@ -0,0 +1,5 @@
1
+ export declare class SameNameClass {
2
+ prop1: number;
3
+ prop2: number;
4
+ prop3: number;
5
+ }
@@ -0,0 +1,5 @@
1
+ export declare class SameNameClass {
2
+ prop1: string;
3
+ prop2: string;
4
+ prop3: string;
5
+ }
@@ -0,0 +1,4 @@
1
+ export declare class ThrowingClass {
2
+ uniqueA: string;
3
+ constructor();
4
+ }
@@ -0,0 +1,4 @@
1
+ export declare class ThrowingClass {
2
+ uniqueB: boolean;
3
+ constructor();
4
+ }
@@ -0,0 +1,30 @@
1
+ export declare enum UserRole {
2
+ ADMIN = "admin",
3
+ USER = "user",
4
+ GUEST = "guest"
5
+ }
6
+ export declare enum OrderStatus {
7
+ PENDING = 0,
8
+ PROCESSING = 1,
9
+ SHIPPED = 2,
10
+ DELIVERED = 3,
11
+ CANCELLED = 4
12
+ }
13
+ export declare enum MixedEnum {
14
+ YES = "yes",
15
+ NO = 0
16
+ }
17
+ export declare class EnumTestEntity {
18
+ role: UserRole;
19
+ status: OrderStatus;
20
+ mixed: MixedEnum;
21
+ }
22
+ export declare class ArrayEnumTestEntity {
23
+ roles: UserRole[];
24
+ statuses: OrderStatus[];
25
+ }
26
+ export declare class PureEnumTestEntity {
27
+ role: UserRole;
28
+ status: OrderStatus;
29
+ mixed: MixedEnum;
30
+ }
@@ -0,0 +1,6 @@
1
+ export declare class Base<T> {
2
+ data: T;
3
+ }
4
+ export declare class ConcreteString extends Base<string> {
5
+ other: number;
6
+ }
@@ -0,0 +1,7 @@
1
+ export declare class AccessorAndModifiers {
2
+ publicProp: string;
3
+ private privateProp;
4
+ protected protectedProp: string;
5
+ static staticProp: string;
6
+ get computedProp(): string;
7
+ }
@@ -1,3 +1,7 @@
1
1
  import './testCases/pure-classes.test';
2
2
  import './testCases/decorated-classes.test';
3
3
  import './testCases/nested-classes.test';
4
+ import './testCases/enum-properties.test';
5
+ import './testCases/collision.test';
6
+ import './testCases/collision-advanced.test';
7
+ import './testCases/generics-and-modifiers.test';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.esm.js CHANGED
@@ -66,14 +66,14 @@ class SchemaTransformer {
66
66
  this.program = ts.createProgram(fileNames, tsOptions);
67
67
  this.checker = this.program.getTypeChecker();
68
68
  }
69
- getPropertiesByClassDeclaration(classNode, visitedDeclarations = new Set()) {
69
+ getPropertiesByClassDeclaration(classNode, visitedDeclarations = new Set(), genericTypeMap = new Map()) {
70
70
  if (visitedDeclarations.has(classNode)) {
71
71
  return [];
72
72
  }
73
73
  visitedDeclarations.add(classNode);
74
74
  // if no heritage clauses, get properties directly from class
75
75
  if (!classNode.heritageClauses) {
76
- return this.getPropertiesByClassMembers(classNode.members, classNode);
76
+ return this.getPropertiesByClassMembers(classNode.members, classNode, genericTypeMap);
77
77
  } // use heritage clauses to get properties from base classes
78
78
  else {
79
79
  const heritageClause = classNode.heritageClauses[0];
@@ -89,28 +89,47 @@ class SchemaTransformer {
89
89
  return [];
90
90
  const declaration = symbol.declarations?.[0];
91
91
  if (declaration && ts.isClassDeclaration(declaration)) {
92
- baseProperties = this.getPropertiesByClassDeclaration(declaration, visitedDeclarations);
92
+ const newGenericTypeMap = new Map();
93
+ if (declaration.typeParameters && type.typeArguments) {
94
+ declaration.typeParameters.forEach((param, index) => {
95
+ const arg = type.typeArguments[index];
96
+ if (arg) {
97
+ const resolvedArg = this.getTypeNodeToString(arg, genericTypeMap);
98
+ newGenericTypeMap.set(param.name.text, resolvedArg);
99
+ }
100
+ });
101
+ }
102
+ baseProperties = this.getPropertiesByClassDeclaration(declaration, visitedDeclarations, newGenericTypeMap);
93
103
  }
94
- properties = this.getPropertiesByClassMembers(classNode.members, classNode);
104
+ properties = this.getPropertiesByClassMembers(classNode.members, classNode, genericTypeMap);
95
105
  return baseProperties.concat(properties);
96
106
  }
97
107
  else {
98
- return this.getPropertiesByClassMembers(classNode.members, classNode);
108
+ return this.getPropertiesByClassMembers(classNode.members, classNode, genericTypeMap);
99
109
  }
100
110
  }
101
111
  }
102
- getPropertiesByClassMembers(members, parentClassNode) {
112
+ getPropertiesByClassMembers(members, parentClassNode, genericTypeMap = new Map()) {
103
113
  const properties = [];
104
114
  for (const member of members) {
105
115
  if (ts.isPropertyDeclaration(member) &&
106
116
  member.name &&
107
117
  ts.isIdentifier(member.name)) {
118
+ // Skip static, private, and protected properties
119
+ if (member.modifiers) {
120
+ const hasExcludedModifier = member.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword ||
121
+ m.kind === ts.SyntaxKind.PrivateKeyword ||
122
+ m.kind === ts.SyntaxKind.ProtectedKeyword);
123
+ if (hasExcludedModifier)
124
+ continue;
125
+ }
108
126
  const propertyName = member.name.text;
109
- const type = this.getPropertyType(member);
127
+ const type = this.getPropertyType(member, genericTypeMap);
110
128
  const decorators = this.extractDecorators(member);
111
129
  const isOptional = !!member.questionToken;
112
130
  const isGeneric = this.isPropertyTypeGeneric(member);
113
- const isPrimitive = this.isPrimitiveType(type);
131
+ const isEnum = this.isEnum(member);
132
+ const isPrimitive = this.isPrimitiveType(type) || isEnum;
114
133
  const isClassType = this.isClassType(member);
115
134
  const isArray = this.isArrayProperty(member);
116
135
  const isTypeLiteral = this.isTypeLiteral(member);
@@ -124,6 +143,7 @@ class SchemaTransformer {
124
143
  isPrimitive,
125
144
  isClassType,
126
145
  isArray,
146
+ isEnum,
127
147
  isRef: false,
128
148
  isTypeLiteral: isTypeLiteral,
129
149
  };
@@ -164,17 +184,21 @@ class SchemaTransformer {
164
184
  }
165
185
  return properties;
166
186
  }
167
- getPropertyType(property) {
187
+ getPropertyType(property, genericTypeMap = new Map()) {
168
188
  if (property.type) {
169
- return this.getTypeNodeToString(property.type);
189
+ return this.getTypeNodeToString(property.type, genericTypeMap);
170
190
  }
171
191
  const type = this.checker.getTypeAtLocation(property);
172
192
  return this.getStringFromType(type);
173
193
  }
174
- getTypeNodeToString(typeNode) {
194
+ getTypeNodeToString(typeNode, genericTypeMap = new Map()) {
175
195
  if (ts.isTypeReferenceNode(typeNode) &&
176
196
  ts.isIdentifier(typeNode.typeName)) {
177
- if (typeNode.typeName.text.toLowerCase() === 'uploadfile') {
197
+ const typeName = typeNode.typeName.text;
198
+ if (genericTypeMap.has(typeName)) {
199
+ return genericTypeMap.get(typeName);
200
+ }
201
+ if (typeName.toLowerCase() === 'uploadfile') {
178
202
  return 'UploadFile';
179
203
  }
180
204
  if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
@@ -199,11 +223,11 @@ class SchemaTransformer {
199
223
  return constants.jsPrimitives.Boolean.type;
200
224
  case ts.SyntaxKind.ArrayType:
201
225
  const arrayType = typeNode;
202
- return `${this.getTypeNodeToString(arrayType.elementType)}[]`;
226
+ return `${this.getTypeNodeToString(arrayType.elementType, genericTypeMap)}[]`;
203
227
  case ts.SyntaxKind.UnionType:
204
228
  // Handle union types like string | null
205
229
  const unionType = typeNode;
206
- const types = unionType.types.map(t => this.getTypeNodeToString(t));
230
+ const types = unionType.types.map(t => this.getTypeNodeToString(t, genericTypeMap));
207
231
  // Filter out null and undefined, return the first meaningful type
208
232
  const meaningfulTypes = types.filter(t => t !== 'null' && t !== 'undefined');
209
233
  if (meaningfulTypes.length > 0 && meaningfulTypes[0]) {
@@ -215,6 +239,10 @@ class SchemaTransformer {
215
239
  return 'object';
216
240
  default:
217
241
  const typeText = typeNode.getText();
242
+ // Check if this is a generic type parameter we can resolve
243
+ if (genericTypeMap && genericTypeMap.has(typeText)) {
244
+ return genericTypeMap.get(typeText);
245
+ }
218
246
  // Handle some common TypeScript utility types
219
247
  if (typeText.startsWith('Date'))
220
248
  return constants.jsPrimitives.Date.type;
@@ -275,9 +303,15 @@ class SchemaTransformer {
275
303
  return true;
276
304
  if (arg.kind === ts.SyntaxKind.FalseKeyword)
277
305
  return false;
278
- return arg.getText();
306
+ return arg;
279
307
  });
280
308
  }
309
+ getSafeDecoratorArgument(arg) {
310
+ if (arg && typeof arg === 'object' && 'kind' in arg) {
311
+ return arg.getText();
312
+ }
313
+ return arg;
314
+ }
281
315
  isPropertyTypeGeneric(property) {
282
316
  if (property.type && this.isGenericTypeFromNode(property.type)) {
283
317
  return true;
@@ -443,37 +477,175 @@ class SchemaTransformer {
443
477
  }
444
478
  return SchemaTransformer.instance;
445
479
  }
446
- getSourceFileByClassName(className, sourceOptions) {
447
- let sourceFiles = [];
448
- if (sourceOptions?.isExternal) {
449
- sourceFiles = this.program.getSourceFiles().filter(sf => {
450
- return (sf.fileName.includes(sourceOptions.packageName) &&
451
- (!sourceOptions.filePath || sf.fileName === sourceOptions.filePath));
452
- });
480
+ getSourceFileByClass(cls, sourceOptions) {
481
+ const className = cls.name;
482
+ const sourceFiles = this.getFilteredSourceFiles(sourceOptions);
483
+ const matches = [];
484
+ for (const sourceFile of sourceFiles) {
485
+ const node = sourceFile.statements.find(stmt => ts.isClassDeclaration(stmt) &&
486
+ stmt.name &&
487
+ stmt.name.text === className);
488
+ if (node) {
489
+ matches.push({ sourceFile, node });
490
+ }
453
491
  }
454
- else {
455
- sourceFiles = this.program.getSourceFiles().filter(sf => {
456
- if (sf.isDeclarationFile)
457
- return false;
458
- if (sf.fileName.includes('.d.ts'))
459
- return false;
460
- if (sf.fileName.includes('node_modules'))
461
- return false;
492
+ if (matches.length === 0) {
493
+ return undefined;
494
+ }
495
+ if (matches.length === 1) {
496
+ return matches[0];
497
+ }
498
+ if (matches.length > 1 && !sourceOptions?.filePath) {
499
+ const bestMatch = this.findBestMatch(cls, matches);
500
+ if (bestMatch) {
501
+ return bestMatch;
502
+ }
503
+ const firstMatch = matches[0];
504
+ if (firstMatch) {
505
+ console.warn(`[ts-class-to-openapi] Warning: Found multiple classes with name '${className}'. Using the first one found in '${firstMatch.sourceFile.fileName}'. To resolve this collision, provide 'sourceOptions.filePath'.`);
506
+ }
507
+ }
508
+ return matches[0];
509
+ }
510
+ checkTypeMatch(value, typeNode) {
511
+ const runtimeType = typeof value;
512
+ if (runtimeType === 'string' &&
513
+ typeNode.kind === ts.SyntaxKind.StringKeyword)
514
+ return true;
515
+ if (runtimeType === 'number' &&
516
+ typeNode.kind === ts.SyntaxKind.NumberKeyword)
517
+ return true;
518
+ if (runtimeType === 'boolean' &&
519
+ typeNode.kind === ts.SyntaxKind.BooleanKeyword)
520
+ return true;
521
+ if (Array.isArray(value) && ts.isArrayTypeNode(typeNode)) {
522
+ if (value.length === 0)
462
523
  return true;
524
+ const firstItem = value[0];
525
+ const elementType = typeNode.elementType;
526
+ return this.checkTypeMatch(firstItem, elementType);
527
+ }
528
+ if (runtimeType === 'object' && value !== null && !Array.isArray(value)) {
529
+ if (ts.isTypeReferenceNode(typeNode) ||
530
+ typeNode.kind === ts.SyntaxKind.ObjectKeyword) {
531
+ return true;
532
+ }
533
+ }
534
+ return false;
535
+ }
536
+ findBestMatch(cls, matches) {
537
+ const runtimeSource = cls.toString();
538
+ const runtimeProperties = new Map();
539
+ const regexProperties = new Set();
540
+ // Try to extract properties from runtime source (assignments in constructor)
541
+ try {
542
+ const regex = /this\.([a-zA-Z0-9_$]+)\s*=/g;
543
+ let match;
544
+ while ((match = regex.exec(runtimeSource)) !== null) {
545
+ regexProperties.add(match[1]);
546
+ }
547
+ }
548
+ catch (e) {
549
+ // Ignore regex errors
550
+ }
551
+ // Try to instantiate the class to find properties
552
+ try {
553
+ const instance = new cls();
554
+ Object.keys(instance).forEach(key => {
555
+ runtimeProperties.set(key, instance[key]);
463
556
  });
464
557
  }
465
- for (const sourceFile of sourceFiles) {
466
- let node;
467
- const found = sourceFile.statements.some(stmt => {
468
- node = stmt;
469
- return (ts.isClassDeclaration(stmt) &&
470
- stmt.name &&
471
- stmt.name.text === className);
558
+ catch (e) {
559
+ // Ignore instantiation errors (e.g. required constructor arguments)
560
+ }
561
+ // Try to get properties from class-validator metadata
562
+ try {
563
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
564
+ const { getMetadataStorage } = require('class-validator');
565
+ const metadata = getMetadataStorage();
566
+ const targetMetadata = metadata.getTargetValidationMetadatas(cls, null, false, false);
567
+ targetMetadata.forEach((m) => {
568
+ if (m.propertyName && !runtimeProperties.has(m.propertyName)) {
569
+ runtimeProperties.set(m.propertyName, undefined);
570
+ }
472
571
  });
473
- if (found) {
474
- return { sourceFile, node: node };
572
+ }
573
+ catch (e) {
574
+ // Ignore if class-validator is not available or fails
575
+ }
576
+ // console.log(`[findBestMatch] Class: ${cls.name}, Runtime Props: ${Array.from(runtimeProperties.keys()).join(', ')}`)
577
+ const scores = matches.map(match => {
578
+ let score = 0;
579
+ for (const member of match.node.members) {
580
+ if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
581
+ if (runtimeSource.includes(member.name.text)) {
582
+ score += 2;
583
+ }
584
+ }
585
+ else if (ts.isPropertyDeclaration(member) &&
586
+ ts.isIdentifier(member.name)) {
587
+ const propName = member.name.text;
588
+ if (runtimeProperties.has(propName)) {
589
+ score += 1;
590
+ const value = runtimeProperties.get(propName);
591
+ if (member.type && this.checkTypeMatch(value, member.type)) {
592
+ score += 5;
593
+ }
594
+ }
595
+ else if (regexProperties.has(propName)) {
596
+ score += 1;
597
+ }
598
+ }
599
+ }
600
+ return { match, score };
601
+ });
602
+ scores.sort((a, b) => b.score - a.score);
603
+ const firstScore = scores[0];
604
+ const secondScore = scores[1];
605
+ if (firstScore && firstScore.score > 0) {
606
+ if (scores.length === 1 ||
607
+ (secondScore && firstScore.score > secondScore.score)) {
608
+ return firstScore.match;
475
609
  }
476
610
  }
611
+ return undefined;
612
+ }
613
+ getFilteredSourceFiles(sourceOptions) {
614
+ if (sourceOptions?.isExternal) {
615
+ return this.program.getSourceFiles().filter(sf => {
616
+ return (sf.fileName.includes(sourceOptions.packageName) &&
617
+ (!sourceOptions.filePath || sf.fileName === sourceOptions.filePath));
618
+ });
619
+ }
620
+ return this.program.getSourceFiles().filter(sf => {
621
+ if (sf.isDeclarationFile)
622
+ return false;
623
+ if (sf.fileName.includes('.d.ts'))
624
+ return false;
625
+ if (sf.fileName.includes('node_modules'))
626
+ return false;
627
+ if (sourceOptions?.filePath &&
628
+ !sf.fileName.includes(sourceOptions.filePath)) {
629
+ return false;
630
+ }
631
+ return true;
632
+ });
633
+ }
634
+ isEnum(propertyDeclaration) {
635
+ if (!propertyDeclaration.type) {
636
+ return false;
637
+ }
638
+ let typeNode = propertyDeclaration.type;
639
+ if (ts.isArrayTypeNode(typeNode)) {
640
+ typeNode = typeNode.elementType;
641
+ }
642
+ if (ts.isTypeReferenceNode(typeNode)) {
643
+ const type = this.checker.getTypeAtLocation(typeNode);
644
+ // console.log('isEnum check:', typeNode.getText(), type.flags)
645
+ return (!!(type.flags & ts.TypeFlags.Enum) ||
646
+ !!(type.flags & ts.TypeFlags.EnumLiteral));
647
+ }
648
+ return false;
477
649
  }
478
650
  isClassType(propertyDeclaration) {
479
651
  // If there's no explicit type annotation, we can't determine reliably
@@ -724,7 +896,58 @@ class SchemaTransformer {
724
896
  transformedSchema.set(declaration.name.text, schema);
725
897
  return schema;
726
898
  }
899
+ getSchemaFromEnum(property) {
900
+ let typeNode = property.originalProperty.type;
901
+ if (ts.isArrayTypeNode(typeNode)) {
902
+ typeNode = typeNode.elementType;
903
+ }
904
+ const type = this.checker.getTypeAtLocation(typeNode);
905
+ if (type.symbol && type.symbol.exports) {
906
+ const values = [];
907
+ type.symbol.exports.forEach(member => {
908
+ const declaration = member.valueDeclaration;
909
+ if (declaration && ts.isEnumMember(declaration)) {
910
+ const value = this.checker.getConstantValue(declaration);
911
+ if (value !== undefined) {
912
+ values.push(value);
913
+ }
914
+ }
915
+ });
916
+ if (values.length > 0) {
917
+ const propertySchema = { type: 'object' };
918
+ propertySchema.enum = values;
919
+ const isString = values.every(v => typeof v === 'string');
920
+ const isNumber = values.every(v => typeof v === 'number');
921
+ if (isString) {
922
+ propertySchema.type = 'string';
923
+ }
924
+ else if (isNumber) {
925
+ propertySchema.type = 'number';
926
+ }
927
+ else {
928
+ propertySchema.type = 'string';
929
+ }
930
+ if (property.isArray) {
931
+ const itemsSchema = { ...propertySchema };
932
+ propertySchema.type = 'array';
933
+ propertySchema.items = itemsSchema;
934
+ delete propertySchema.enum;
935
+ return propertySchema;
936
+ }
937
+ else {
938
+ return propertySchema;
939
+ }
940
+ }
941
+ }
942
+ return undefined;
943
+ }
727
944
  getSchemaFromPrimitive(property) {
945
+ if (property.isEnum) {
946
+ const enumSchema = this.getSchemaFromEnum(property);
947
+ if (enumSchema) {
948
+ return enumSchema;
949
+ }
950
+ }
728
951
  const propertySchema = { type: 'object' };
729
952
  const propertyType = property.type.toLowerCase().replace('[]', '').trim();
730
953
  let isFile = false;
@@ -812,8 +1035,40 @@ class SchemaTransformer {
812
1035
  ts.isTypeReferenceNode(typeNode) // Omit, Pick, Partial, etc.
813
1036
  );
814
1037
  }
815
- //Todo: implement properly
816
- applyEnumDecorator(decorator, schema) { }
1038
+ applyEnumDecorator(decorator, schema) {
1039
+ if (decorator.arguments.length === 0)
1040
+ return;
1041
+ const arg = decorator.arguments[0];
1042
+ if (arg && typeof arg === 'object' && 'kind' in arg) {
1043
+ const type = this.checker.getTypeAtLocation(arg);
1044
+ if (type.symbol && type.symbol.exports) {
1045
+ const values = [];
1046
+ type.symbol.exports.forEach(member => {
1047
+ const declaration = member.valueDeclaration;
1048
+ if (declaration && ts.isEnumMember(declaration)) {
1049
+ const value = this.checker.getConstantValue(declaration);
1050
+ if (value !== undefined) {
1051
+ values.push(value);
1052
+ }
1053
+ }
1054
+ });
1055
+ if (values.length > 0) {
1056
+ schema.enum = values;
1057
+ const isString = values.every(v => typeof v === 'string');
1058
+ const isNumber = values.every(v => typeof v === 'number');
1059
+ if (isString) {
1060
+ schema.type = 'string';
1061
+ }
1062
+ else if (isNumber) {
1063
+ schema.type = 'number';
1064
+ }
1065
+ else {
1066
+ schema.type = 'string';
1067
+ }
1068
+ }
1069
+ }
1070
+ }
1071
+ }
817
1072
  applyDecorators(property, schema) {
818
1073
  for (const decorator of property.decorators) {
819
1074
  const decoratorName = decorator.name;
@@ -877,22 +1132,22 @@ class SchemaTransformer {
877
1132
  property.isOptional = true;
878
1133
  break;
879
1134
  case constants.validatorDecorators.MinLength.name:
880
- schema.minLength = decorator.arguments[0];
1135
+ schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
881
1136
  break;
882
1137
  case constants.validatorDecorators.MaxLength.name:
883
- schema.maxLength = decorator.arguments[0];
1138
+ schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
884
1139
  break;
885
1140
  case constants.validatorDecorators.Length.name:
886
- schema.minLength = decorator.arguments[0];
1141
+ schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
887
1142
  if (decorator.arguments[1]) {
888
- schema.maxLength = decorator.arguments[1];
1143
+ schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[1]);
889
1144
  }
890
1145
  break;
891
1146
  case constants.validatorDecorators.Min.name:
892
- schema.minimum = decorator.arguments[0];
1147
+ schema.minimum = this.getSafeDecoratorArgument(decorator.arguments[0]);
893
1148
  break;
894
1149
  case constants.validatorDecorators.Max.name:
895
- schema.maximum = decorator.arguments[0];
1150
+ schema.maximum = this.getSafeDecoratorArgument(decorator.arguments[0]);
896
1151
  break;
897
1152
  case constants.validatorDecorators.IsPositive.name:
898
1153
  schema.minimum = 0;
@@ -905,20 +1160,25 @@ class SchemaTransformer {
905
1160
  property.isOptional = false;
906
1161
  break;
907
1162
  case constants.validatorDecorators.ArrayMinSize.name:
908
- schema.minItems = decorator.arguments[0];
1163
+ schema.minItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
909
1164
  break;
910
1165
  case constants.validatorDecorators.ArrayMaxSize.name:
911
- schema.maxItems = decorator.arguments[0];
1166
+ schema.maxItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
912
1167
  break;
913
1168
  case constants.validatorDecorators.IsEnum.name:
914
- this.applyEnumDecorator(decorator, schema);
1169
+ if (!property.isArray) {
1170
+ this.applyEnumDecorator(decorator, schema);
1171
+ }
1172
+ else if (schema.items) {
1173
+ this.applyEnumDecorator(decorator, schema.items);
1174
+ }
915
1175
  break;
916
1176
  }
917
1177
  }
918
1178
  }
919
1179
  transform(cls, sourceOptions) {
920
1180
  let schema = { type: 'object', properties: {} };
921
- const result = this.getSourceFileByClassName(cls.name, sourceOptions);
1181
+ const result = this.getSourceFileByClass(cls, sourceOptions);
922
1182
  if (!result?.sourceFile) {
923
1183
  console.warn(`Class ${cls.name} not found in any source file.`);
924
1184
  return { name: cls.name, schema: {} };