@necessarylion/adonis-autoswagger 0.0.1

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.
@@ -0,0 +1,1107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EnumParser = exports.InterfaceParser = exports.ValidatorParser = exports.ModelParser = exports.RouteParser = exports.CommentParser = void 0;
7
+ const http_status_code_1 = __importDefault(require("http-status-code"));
8
+ const helpers_1 = require("./helpers");
9
+ const util_1 = __importDefault(require("util"));
10
+ const extract_comments_1 = __importDefault(require("extract-comments"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const lodash_1 = require("lodash");
13
+ const example_1 = __importDefault(require("./example"));
14
+ const types_1 = require("./types");
15
+ const lodash_2 = __importDefault(require("lodash"));
16
+ class CommentParser {
17
+ parsedFiles = {};
18
+ exampleGenerator;
19
+ options;
20
+ constructor(options) {
21
+ this.options = options;
22
+ }
23
+ parseAnnotations(lines) {
24
+ let summary = "";
25
+ let tag = "";
26
+ let description = "";
27
+ let operationId;
28
+ let responses = {};
29
+ let requestBody;
30
+ let parameters = {};
31
+ let headers = {};
32
+ lines.forEach((line) => {
33
+ if (line.startsWith("@summary")) {
34
+ summary = line.replace("@summary ", "");
35
+ }
36
+ if (line.startsWith("@tag")) {
37
+ tag = line.replace("@tag ", "");
38
+ }
39
+ if (line.startsWith("@description")) {
40
+ description = line.replace("@description ", "");
41
+ }
42
+ if (line.startsWith("@operationId")) {
43
+ operationId = line.replace("@operationId ", "");
44
+ }
45
+ if (line.startsWith("@responseBody")) {
46
+ responses = {
47
+ ...responses,
48
+ ...this.parseResponseBody(line),
49
+ };
50
+ }
51
+ if (line.startsWith("@responseHeader")) {
52
+ const header = this.parseResponseHeader(line);
53
+ if (header === null) {
54
+ console.error("Error with line: " + line);
55
+ return;
56
+ }
57
+ headers[header["status"]] = {
58
+ ...headers[header["status"]],
59
+ ...header["header"],
60
+ };
61
+ }
62
+ if (line.startsWith("@requestBody")) {
63
+ requestBody = this.parseBody(line, "requestBody");
64
+ }
65
+ if (line.startsWith("@requestFormDataBody")) {
66
+ const parsedBody = this.parseRequestFormDataBody(line);
67
+ if (parsedBody) {
68
+ requestBody = parsedBody;
69
+ }
70
+ }
71
+ if (line.startsWith("@param")) {
72
+ parameters = { ...parameters, ...this.parseParam(line) };
73
+ }
74
+ });
75
+ for (const [key, value] of Object.entries(responses)) {
76
+ if (typeof headers[key] !== undefined) {
77
+ responses[key]["headers"] = headers[key];
78
+ }
79
+ if (!responses[key]["description"]) {
80
+ responses[key]["description"] = `Returns **${key}** (${http_status_code_1.default.getMessage(key)}) as **${Object.entries(responses[key]["content"])[0][0]}**`;
81
+ }
82
+ }
83
+ return {
84
+ description,
85
+ responses,
86
+ requestBody,
87
+ parameters,
88
+ summary,
89
+ operationId,
90
+ tag,
91
+ };
92
+ }
93
+ parseParam(line) {
94
+ let where = "path";
95
+ let required = true;
96
+ let type = "string";
97
+ let example = null;
98
+ let enums = [];
99
+ if (line.startsWith("@paramUse")) {
100
+ let use = (0, helpers_1.getBetweenBrackets)(line, "paramUse");
101
+ const used = use.split(",");
102
+ let h = [];
103
+ used.forEach((u) => {
104
+ if (typeof this.options.common.parameters[u] === "undefined") {
105
+ return;
106
+ }
107
+ const common = this.options.common.parameters[u];
108
+ h = [...h, ...common];
109
+ });
110
+ return h;
111
+ }
112
+ if (line.startsWith("@paramPath")) {
113
+ required = false;
114
+ }
115
+ if (line.startsWith("@paramQuery")) {
116
+ required = false;
117
+ }
118
+ let m = line.match("@param([a-zA-Z]*)");
119
+ if (m !== null) {
120
+ where = m[1].toLowerCase();
121
+ line = line.replace(m[0] + " ", "");
122
+ }
123
+ let [param, des, meta] = line.split(" - ");
124
+ if (typeof param === "undefined") {
125
+ return;
126
+ }
127
+ if (typeof des === "undefined") {
128
+ des = "";
129
+ }
130
+ if (typeof meta !== "undefined") {
131
+ if (meta.includes("@required")) {
132
+ required = true;
133
+ }
134
+ let en = (0, helpers_1.getBetweenBrackets)(meta, "enum");
135
+ example = (0, helpers_1.getBetweenBrackets)(meta, "example");
136
+ const mtype = (0, helpers_1.getBetweenBrackets)(meta, "type");
137
+ if (mtype !== "") {
138
+ type = mtype;
139
+ }
140
+ if (en !== "") {
141
+ enums = en.split(",");
142
+ example = enums[0];
143
+ }
144
+ }
145
+ let p = {
146
+ in: where,
147
+ name: param,
148
+ description: des,
149
+ schema: {
150
+ example: example,
151
+ type: type,
152
+ },
153
+ required: required,
154
+ };
155
+ if (enums.length > 1) {
156
+ p["schema"]["enum"] = enums;
157
+ }
158
+ return { [param]: p };
159
+ }
160
+ parseResponseHeader(responseLine) {
161
+ let description = "";
162
+ let example = "";
163
+ let type = "string";
164
+ let enums = [];
165
+ const line = responseLine.replace("@responseHeader ", "");
166
+ let [status, name, desc, meta] = line.split(" - ");
167
+ if (typeof status === "undefined" || typeof name === "undefined") {
168
+ return null;
169
+ }
170
+ if (typeof desc !== "undefined") {
171
+ description = desc;
172
+ }
173
+ if (name.includes("@use")) {
174
+ let use = (0, helpers_1.getBetweenBrackets)(name, "use");
175
+ const used = use.split(",");
176
+ let h = {};
177
+ used.forEach((u) => {
178
+ if (typeof this.options.common.headers[u] === "undefined") {
179
+ return;
180
+ }
181
+ const common = this.options.common.headers[u];
182
+ h = { ...h, ...common };
183
+ });
184
+ return {
185
+ status: status,
186
+ header: h,
187
+ };
188
+ }
189
+ if (typeof meta !== "undefined") {
190
+ example = (0, helpers_1.getBetweenBrackets)(meta, "example");
191
+ const mtype = (0, helpers_1.getBetweenBrackets)(meta, "type");
192
+ if (mtype !== "") {
193
+ type = mtype;
194
+ }
195
+ }
196
+ if (example === "" || example === null) {
197
+ switch (type) {
198
+ case "string":
199
+ example = "string";
200
+ break;
201
+ case "integer":
202
+ example = 1;
203
+ break;
204
+ case "float":
205
+ example = 1.5;
206
+ break;
207
+ }
208
+ }
209
+ let h = {
210
+ schema: { type: type, example: example },
211
+ description: description,
212
+ };
213
+ if (enums.length > 1) {
214
+ h["schema"]["enum"] = enums;
215
+ }
216
+ return {
217
+ status: status,
218
+ header: {
219
+ [name]: h,
220
+ },
221
+ };
222
+ }
223
+ parseResponseBody(responseLine) {
224
+ let responses = {};
225
+ const line = responseLine.replace("@responseBody ", "");
226
+ let [status, res, desc] = line.split(" - ");
227
+ if (typeof status === "undefined")
228
+ return;
229
+ responses[status] = this.parseBody(res, "responseBody");
230
+ responses[status]["description"] = desc;
231
+ return responses;
232
+ }
233
+ parseRequestFormDataBody(rawLine) {
234
+ const line = rawLine.replace("@requestFormDataBody ", "");
235
+ let json = {}, required = [];
236
+ const isJson = (0, helpers_1.isJSONString)(line);
237
+ if (!isJson) {
238
+ // try to get json from reference
239
+ let rawRef = line.substring(line.indexOf("<") + 1, line.lastIndexOf(">"));
240
+ const cleandRef = rawRef.replace("[]", "");
241
+ if (cleandRef === "") {
242
+ return;
243
+ }
244
+ const parsedRef = this.exampleGenerator.parseRef(line, true);
245
+ let props = [];
246
+ const ref = this.exampleGenerator.schemas[cleandRef];
247
+ const ks = [];
248
+ if (ref.required && Array.isArray(ref.required))
249
+ required.push(...ref.required);
250
+ Object.entries(ref.properties).map(([key, value]) => {
251
+ if (typeof parsedRef[key] === "undefined") {
252
+ return;
253
+ }
254
+ ks.push(key);
255
+ if (value["required"])
256
+ required.push(key);
257
+ props.push({
258
+ [key]: {
259
+ type: typeof value["type"] === "undefined" ? "string" : value["type"],
260
+ format: typeof value["format"] === "undefined"
261
+ ? "string"
262
+ : value["format"],
263
+ },
264
+ });
265
+ });
266
+ const p = props.reduce((acc, curr) => ({ ...acc, ...curr }), {});
267
+ const appends = Object.keys(parsedRef).filter((k) => !ks.includes(k));
268
+ json = p;
269
+ if (appends.length > 0) {
270
+ appends.forEach((a) => {
271
+ json[a] = parsedRef[a];
272
+ });
273
+ }
274
+ }
275
+ else {
276
+ json = JSON.parse(line);
277
+ for (let key in json) {
278
+ if (json[key].required === "true") {
279
+ required.push(key);
280
+ }
281
+ }
282
+ }
283
+ // No need to try/catch this JSON.parse as we already did that in the isJSONString function
284
+ return {
285
+ content: {
286
+ "multipart/form-data": {
287
+ schema: {
288
+ type: "object",
289
+ properties: json,
290
+ required,
291
+ },
292
+ },
293
+ },
294
+ };
295
+ }
296
+ parseBody(rawLine, type) {
297
+ let line = rawLine.replace(`@${type} `, "");
298
+ const isJson = (0, helpers_1.isJSONString)(line);
299
+ if (isJson) {
300
+ // No need to try/catch this JSON.parse as we already did that in the isJSONString function
301
+ const json = JSON.parse(line);
302
+ const o = this.jsonToObj(json);
303
+ return {
304
+ content: {
305
+ "application/json": {
306
+ schema: {
307
+ type: Array.isArray(json) ? "array" : "object",
308
+ ...(Array.isArray(json) ? { items: this.arrayItems(json) } : o),
309
+ },
310
+ example: this.exampleGenerator.jsonToRef(json),
311
+ },
312
+ },
313
+ };
314
+ }
315
+ return this.exampleGenerator.parseRef(line);
316
+ }
317
+ arrayItems(json) {
318
+ const oneOf = [];
319
+ const t = typeof json[0];
320
+ if (t === "string") {
321
+ json.forEach((j) => {
322
+ const value = this.exampleGenerator.parseRef(j);
323
+ if (lodash_2.default.has(value, "content.application/json.schema.$ref")) {
324
+ oneOf.push({
325
+ $ref: value["content"]["application/json"]["schema"]["$ref"],
326
+ });
327
+ }
328
+ });
329
+ }
330
+ if (oneOf.length > 0) {
331
+ return { oneOf: oneOf };
332
+ }
333
+ return { type: typeof json[0] };
334
+ }
335
+ jsonToObj(json) {
336
+ const o = {
337
+ type: "object",
338
+ properties: Object.keys(json)
339
+ .map((key) => {
340
+ const t = typeof json[key];
341
+ const v = json[key];
342
+ let value = v;
343
+ if (t === "object") {
344
+ value = this.jsonToObj(json[key]);
345
+ }
346
+ if (t === "string" && v.includes("<") && v.includes(">")) {
347
+ value = this.exampleGenerator.parseRef(v);
348
+ if (v.includes("[]")) {
349
+ let ref = "";
350
+ if (lodash_2.default.has(value, "content.application/json.schema.$ref")) {
351
+ ref = value["content"]["application/json"]["schema"]["$ref"];
352
+ }
353
+ if (lodash_2.default.has(value, "content.application/json.schema.items.$ref")) {
354
+ ref =
355
+ value["content"]["application/json"]["schema"]["items"]["$ref"];
356
+ }
357
+ value = {
358
+ type: "array",
359
+ items: {
360
+ $ref: ref,
361
+ },
362
+ };
363
+ }
364
+ else {
365
+ value = {
366
+ $ref: value["content"]["application/json"]["schema"]["$ref"],
367
+ };
368
+ }
369
+ }
370
+ return {
371
+ [key]: value,
372
+ };
373
+ })
374
+ .reduce((acc, curr) => ({ ...acc, ...curr }), {}),
375
+ };
376
+ // console.dir(o, { depth: null });
377
+ // console.log(json);
378
+ return o;
379
+ }
380
+ async getAnnotations(file, action) {
381
+ let annotations = {};
382
+ let newdata = "";
383
+ if (typeof file === "undefined")
384
+ return;
385
+ if (typeof this.parsedFiles[file] !== "undefined") {
386
+ newdata = this.parsedFiles[file];
387
+ }
388
+ else {
389
+ try {
390
+ const readFile = util_1.default.promisify(fs_1.default.readFile);
391
+ const data = await readFile(file, "utf8");
392
+ for (const line of data.split("\n")) {
393
+ const l = line.trim();
394
+ if (!l.startsWith("@")) {
395
+ newdata += l + "\n";
396
+ }
397
+ }
398
+ this.parsedFiles[file] = newdata;
399
+ }
400
+ catch (e) {
401
+ console.error("\x1b[31m✗ File not found\x1b[0m", file);
402
+ }
403
+ }
404
+ const comments = (0, extract_comments_1.default)(newdata);
405
+ if (comments.length > 0) {
406
+ comments.forEach((comment) => {
407
+ if (comment.type !== "BlockComment")
408
+ return;
409
+ let lines = comment.value.split("\n").filter((l) => l != "");
410
+ // fix for decorators
411
+ if (lines[0].trim() !== "@" + action)
412
+ return;
413
+ lines = lines.filter((l) => l != "");
414
+ annotations[action] = this.parseAnnotations(lines);
415
+ });
416
+ }
417
+ return annotations;
418
+ }
419
+ }
420
+ exports.CommentParser = CommentParser;
421
+ class RouteParser {
422
+ options;
423
+ constructor(options) {
424
+ this.options = options;
425
+ }
426
+ /*
427
+ extract path-variables, tags and the uri-pattern
428
+ */
429
+ extractInfos(p) {
430
+ let parameters = {};
431
+ let pattern = "";
432
+ let tags = [];
433
+ let required;
434
+ const split = p.split("/");
435
+ if (split.length > this.options.tagIndex) {
436
+ tags = [split[this.options.tagIndex].toUpperCase()];
437
+ }
438
+ split.forEach((part) => {
439
+ if (part.startsWith(":")) {
440
+ required = !part.endsWith("?");
441
+ const param = part.replace(":", "").replace("?", "");
442
+ part = "{" + param + "}";
443
+ parameters = {
444
+ ...parameters,
445
+ [param]: {
446
+ in: "path",
447
+ name: param,
448
+ schema: {
449
+ type: "string",
450
+ },
451
+ required: required,
452
+ },
453
+ };
454
+ }
455
+ pattern += "/" + part;
456
+ });
457
+ if (pattern.endsWith("/")) {
458
+ pattern = pattern.slice(0, -1);
459
+ }
460
+ return { tags, parameters, pattern };
461
+ }
462
+ }
463
+ exports.RouteParser = RouteParser;
464
+ class ModelParser {
465
+ exampleGenerator;
466
+ snakeCase;
467
+ constructor(snakeCase) {
468
+ this.snakeCase = snakeCase;
469
+ this.exampleGenerator = new example_1.default({});
470
+ }
471
+ parseModelProperties(data) {
472
+ let props = {};
473
+ let required = [];
474
+ // remove empty lines
475
+ data = data.replace(/\t/g, "").replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, "");
476
+ const lines = data.split("\n");
477
+ let softDelete = false;
478
+ let name = "";
479
+ lines.forEach((line, index) => {
480
+ line = line.trim();
481
+ // skip comments
482
+ if (line.startsWith("export default class")) {
483
+ name = line.split(" ")[3];
484
+ }
485
+ if (line.includes("@swagger-softdelete") ||
486
+ line.includes("SoftDeletes")) {
487
+ softDelete = true;
488
+ }
489
+ if (line.startsWith("//") ||
490
+ line.startsWith("/*") ||
491
+ line.startsWith("*") ||
492
+ line.startsWith("public static ") ||
493
+ line.startsWith("private static ") ||
494
+ line.startsWith("static "))
495
+ return;
496
+ if (index > 0 && lines[index - 1].includes("serializeAs: null"))
497
+ return;
498
+ if (index > 0 && lines[index - 1].includes("@no-swagger"))
499
+ return;
500
+ if (!line.startsWith("public ") &&
501
+ !line.startsWith("public get") &&
502
+ !line.includes("declare "))
503
+ return;
504
+ let s = [];
505
+ if (line.includes("declare ")) {
506
+ s = line.split("declare ");
507
+ }
508
+ if (line.startsWith("public ")) {
509
+ if (line.startsWith("public get")) {
510
+ s = line.split("public get");
511
+ let s2 = s[1].replace(/;/g, "").split(":");
512
+ }
513
+ else {
514
+ s = line.split("public ");
515
+ }
516
+ }
517
+ let s2 = s[1].replace(/;/g, "").split(":");
518
+ let field = s2[0];
519
+ let type = s2[1] || "";
520
+ type = type.trim();
521
+ let enums = [];
522
+ let format = "";
523
+ let keyprops = {};
524
+ let example = null;
525
+ if (index > 0 && lines[index - 1].includes("@enum")) {
526
+ const l = lines[index - 1];
527
+ let en = (0, helpers_1.getBetweenBrackets)(l, "enum");
528
+ if (en !== "") {
529
+ enums = en.split(",");
530
+ example = enums[0];
531
+ }
532
+ }
533
+ if (index > 0 && lines[index - 1].includes("@format")) {
534
+ const l = lines[index - 1];
535
+ let en = (0, helpers_1.getBetweenBrackets)(l, "format");
536
+ if (en !== "") {
537
+ format = en;
538
+ }
539
+ }
540
+ if (index > 0 && lines[index - 1].includes("@example")) {
541
+ const l = lines[index - 1];
542
+ let match = l.match(/example\(([^()]*)\)/g);
543
+ if (match !== null) {
544
+ const m = match[0].replace("example(", "").replace(")", "");
545
+ example = m;
546
+ if (type === "number") {
547
+ example = parseInt(m);
548
+ }
549
+ }
550
+ }
551
+ if (index > 0 && lines[index - 1].includes("@required")) {
552
+ required.push(field);
553
+ }
554
+ if (index > 0 && lines[index - 1].includes("@props")) {
555
+ const l = lines[index - 1].replace("@props", "props");
556
+ const j = (0, helpers_1.getBetweenBrackets)(l, "props");
557
+ if ((0, helpers_1.isJSONString)(j)) {
558
+ keyprops = JSON.parse(j);
559
+ }
560
+ }
561
+ if (typeof type === "undefined") {
562
+ type = "string";
563
+ format = "";
564
+ }
565
+ field = field.trim();
566
+ type = type.trim();
567
+ //TODO: make oneOf
568
+ if (type.includes(" | ")) {
569
+ const types = type.split(" | ");
570
+ type = types.filter((t) => t !== "null")[0];
571
+ }
572
+ field = field.replace("()", "");
573
+ field = field.replace("get ", "");
574
+ type = type.replace("{", "").trim();
575
+ if (this.snakeCase) {
576
+ field = (0, lodash_1.snakeCase)(field);
577
+ }
578
+ let indicator = "type";
579
+ if (example === null) {
580
+ example = "string";
581
+ }
582
+ // if relation to another model
583
+ if (type.includes("typeof")) {
584
+ s = type.split("typeof ");
585
+ type = "#/components/schemas/" + s[1].slice(0, -1);
586
+ indicator = "$ref";
587
+ }
588
+ else {
589
+ if (types_1.standardTypes.includes(type.toLowerCase())) {
590
+ type = type.toLowerCase();
591
+ }
592
+ else {
593
+ // assume its a custom interface
594
+ indicator = "$ref";
595
+ type = "#/components/schemas/" + type;
596
+ }
597
+ }
598
+ type = type.trim();
599
+ let isArray = false;
600
+ if (line.includes("HasMany") ||
601
+ line.includes("ManyToMany") ||
602
+ line.includes("HasManyThrough") ||
603
+ type.includes("[]")) {
604
+ isArray = true;
605
+ if (type.slice(type.length - 2, type.length) === "[]") {
606
+ type = type.split("[]")[0];
607
+ }
608
+ }
609
+ if (example === null || example === "string") {
610
+ example =
611
+ this.exampleGenerator.exampleByField(field) ||
612
+ this.exampleGenerator.exampleByType(type);
613
+ }
614
+ if (type === "datetime") {
615
+ indicator = "type";
616
+ type = "string";
617
+ format = "date-time";
618
+ }
619
+ if (type === "date") {
620
+ indicator = "type";
621
+ type = "string";
622
+ format = "date";
623
+ }
624
+ if (field === "email") {
625
+ indicator = "type";
626
+ type = "string";
627
+ format = "email";
628
+ }
629
+ if (field === "password") {
630
+ indicator = "type";
631
+ type = "string";
632
+ format = "password";
633
+ }
634
+ if (enums.length > 0) {
635
+ indicator = "type";
636
+ type = "string";
637
+ }
638
+ if (type === "any") {
639
+ indicator = "$ref";
640
+ type = "#/components/schemas/Any";
641
+ }
642
+ let prop = {};
643
+ if (type === "integer" || type === "number") {
644
+ if (example === null || example === "string") {
645
+ example = Math.floor(Math.random() * 1000);
646
+ }
647
+ }
648
+ if (type === "boolean") {
649
+ example = true;
650
+ }
651
+ prop[indicator] = type;
652
+ prop["example"] = example;
653
+ // if array
654
+ if (isArray) {
655
+ props[field] = { type: "array", items: prop };
656
+ }
657
+ else {
658
+ props[field] = prop;
659
+ if (format !== "") {
660
+ props[field]["format"] = format;
661
+ }
662
+ }
663
+ Object.entries(keyprops).map(([key, value]) => {
664
+ props[field][key] = value;
665
+ });
666
+ if (enums.length > 0) {
667
+ props[field]["enum"] = enums;
668
+ }
669
+ });
670
+ if (softDelete) {
671
+ props["deleted_at"] = {
672
+ type: "string",
673
+ format: "date-time",
674
+ example: "2021-03-23T16:13:08.489+01:00",
675
+ };
676
+ }
677
+ return { name: name, props: props, required: required };
678
+ }
679
+ }
680
+ exports.ModelParser = ModelParser;
681
+ class ValidatorParser {
682
+ exampleGenerator;
683
+ constructor() {
684
+ this.exampleGenerator = new example_1.default({});
685
+ }
686
+ async validatorToObject(validator) {
687
+ // console.dir(validator.toJSON()["refs"], { depth: null });
688
+ // console.dir(json, { depth: null });
689
+ const obj = {
690
+ type: "object",
691
+ properties: this.parseSchema(validator.toJSON()["schema"]["schema"], validator.toJSON()["refs"]),
692
+ };
693
+ // console.dir(obj, { depth: null });
694
+ const testObj = this.objToTest(obj["properties"]);
695
+ return await this.parsePropsAndMeta(obj, testObj, validator);
696
+ }
697
+ async parsePropsAndMeta(obj, testObj, validator) {
698
+ // console.log(Object.keys(errors));
699
+ const { SimpleMessagesProvider } = await import("@vinejs/vine");
700
+ const [e] = await validator.tryValidate(testObj, {
701
+ messagesProvider: new SimpleMessagesProvider({
702
+ required: "REQUIRED",
703
+ string: "TYPE",
704
+ object: "TYPE",
705
+ number: "TYPE",
706
+ boolean: "TYPE",
707
+ }),
708
+ });
709
+ // if no errors, this means all object-fields are of type number (which we use by default)
710
+ // and we can return the object
711
+ if (e === null) {
712
+ obj["example"] = testObj;
713
+ return obj;
714
+ }
715
+ const msgs = e.messages;
716
+ for (const m of msgs) {
717
+ const err = m["message"];
718
+ let objField = m["field"].replace(".", ".properties.");
719
+ if (m["field"].includes(".0")) {
720
+ objField = objField.replaceAll(`.0`, ".items");
721
+ }
722
+ if (err === "TYPE") {
723
+ lodash_2.default.set(obj["properties"], objField, {
724
+ ...lodash_2.default.get(obj["properties"], objField),
725
+ type: m["rule"],
726
+ example: this.exampleGenerator.exampleByType(m["rule"]),
727
+ });
728
+ if (m["rule"] === "string") {
729
+ if (lodash_2.default.get(obj["properties"], objField)["minimum"]) {
730
+ lodash_2.default.set(obj["properties"], objField, {
731
+ ...lodash_2.default.get(obj["properties"], objField),
732
+ minLength: lodash_2.default.get(obj["properties"], objField)["minimum"],
733
+ });
734
+ lodash_2.default.unset(obj["properties"], objField + ".minimum");
735
+ }
736
+ if (lodash_2.default.get(obj["properties"], objField)["maximum"]) {
737
+ lodash_2.default.set(obj["properties"], objField, {
738
+ ...lodash_2.default.get(obj["properties"], objField),
739
+ maxLength: lodash_2.default.get(obj["properties"], objField)["maximum"],
740
+ });
741
+ lodash_2.default.unset(obj["properties"], objField + ".maximum");
742
+ }
743
+ }
744
+ lodash_2.default.set(testObj, m["field"], this.exampleGenerator.exampleByType(m["rule"]));
745
+ }
746
+ if (err === "FORMAT") {
747
+ lodash_2.default.set(obj["properties"], objField, {
748
+ ...lodash_2.default.get(obj["properties"], objField),
749
+ format: m["rule"],
750
+ type: "string",
751
+ example: this.exampleGenerator.exampleByValidatorRule(m["rule"]),
752
+ });
753
+ lodash_2.default.set(testObj, m["field"], this.exampleGenerator.exampleByValidatorRule(m["rule"]));
754
+ }
755
+ }
756
+ // console.dir(obj, { depth: null });
757
+ obj["example"] = testObj;
758
+ return obj;
759
+ }
760
+ objToTest(obj) {
761
+ const res = {};
762
+ Object.keys(obj).forEach((key) => {
763
+ if (obj[key]["type"] === "object") {
764
+ res[key] = this.objToTest(obj[key]["properties"]);
765
+ }
766
+ else if (obj[key]["type"] === "array") {
767
+ if (obj[key]["items"]["type"] === "object") {
768
+ res[key] = [this.objToTest(obj[key]["items"]["properties"])];
769
+ }
770
+ else {
771
+ res[key] = [obj[key]["items"]["example"]];
772
+ }
773
+ }
774
+ else {
775
+ res[key] = obj[key]["example"];
776
+ }
777
+ });
778
+ return res;
779
+ }
780
+ parseSchema(json, refs) {
781
+ const obj = {};
782
+ for (const p of json["properties"]) {
783
+ let meta = {};
784
+ for (const v of p["validations"]) {
785
+ if (refs[v["ruleFnId"]].options?.example) {
786
+ meta = { ...meta, example: refs[v["ruleFnId"]].options.example };
787
+ }
788
+ if (refs[v["ruleFnId"]].options?.min) {
789
+ meta = { ...meta, minimum: refs[v["ruleFnId"]].options.min };
790
+ }
791
+ if (refs[v["ruleFnId"]].options?.max) {
792
+ meta = { ...meta, maximum: refs[v["ruleFnId"]].options.max };
793
+ }
794
+ if (refs[v["ruleFnId"]].options?.choices) {
795
+ meta = { ...meta, choices: refs[v["ruleFnId"]].options.choices };
796
+ }
797
+ if (refs[v["ruleFnId"]].options?.toString().includes("/")) {
798
+ meta = { ...meta, pattern: refs[v["ruleFnId"]].options.toString() };
799
+ }
800
+ }
801
+ // console.dir(p, { depth: null });
802
+ // console.dir(validations, { depth: null });
803
+ // console.log(min, max, choices, regex);
804
+ obj[p["fieldName"]] =
805
+ p["type"] === "object"
806
+ ? { type: "object", properties: this.parseSchema(p, refs) }
807
+ : p["type"] === "array"
808
+ ? {
809
+ type: "array",
810
+ items: p["each"]["type"] === "object"
811
+ ? {
812
+ type: "object",
813
+ properties: this.parseSchema(p["each"], refs),
814
+ }
815
+ : {
816
+ type: "number",
817
+ example: meta.example ?? meta.minimum ?? this.exampleGenerator.exampleByType("number"),
818
+ ...meta,
819
+ },
820
+ }
821
+ : {
822
+ type: "number",
823
+ example: meta.example ?? meta.minimum ?? this.exampleGenerator.exampleByType("number"),
824
+ ...meta,
825
+ };
826
+ if (!p["isOptional"])
827
+ obj[p["fieldName"]]["required"] = true;
828
+ }
829
+ return obj;
830
+ }
831
+ }
832
+ exports.ValidatorParser = ValidatorParser;
833
+ class InterfaceParser {
834
+ exampleGenerator;
835
+ snakeCase;
836
+ schemas = {};
837
+ constructor(snakeCase, schemas = {}) {
838
+ this.snakeCase = snakeCase;
839
+ this.exampleGenerator = new example_1.default({});
840
+ this.schemas = schemas;
841
+ }
842
+ objToExample(obj) {
843
+ let example = {};
844
+ Object.entries(obj).map(([key, value]) => {
845
+ if (typeof value === "object") {
846
+ example[key] = this.objToExample(value);
847
+ }
848
+ else {
849
+ example[key] = this.exampleGenerator.exampleByType(value);
850
+ if (example[key] === null) {
851
+ example[key] = this.exampleGenerator.exampleByField(key);
852
+ }
853
+ }
854
+ });
855
+ return example;
856
+ }
857
+ parseProps(obj) {
858
+ const no = {};
859
+ Object.entries(obj).map(([f, value]) => {
860
+ if (typeof value === "object") {
861
+ no[f.replaceAll("?", "")] = {
862
+ type: "object",
863
+ nullable: f.includes("?"),
864
+ properties: this.parseProps(value),
865
+ example: this.objToExample(value),
866
+ };
867
+ }
868
+ else {
869
+ no[f.replaceAll("?", "")] = {
870
+ ...this.parseType(value, f),
871
+ };
872
+ }
873
+ });
874
+ return no;
875
+ }
876
+ getInheritedProperties(baseType) {
877
+ if (this.schemas[baseType]?.properties) {
878
+ return {
879
+ properties: this.schemas[baseType].properties,
880
+ required: this.schemas[baseType].required || []
881
+ };
882
+ }
883
+ const cleanType = baseType
884
+ .split('/')
885
+ .pop()
886
+ ?.replace('.ts', '')
887
+ ?.replace(/^[#@]/, '');
888
+ if (!cleanType)
889
+ return { properties: {}, required: [] };
890
+ if (this.schemas[cleanType]?.properties) {
891
+ return {
892
+ properties: this.schemas[cleanType].properties,
893
+ required: this.schemas[cleanType].required || []
894
+ };
895
+ }
896
+ const variations = [
897
+ cleanType,
898
+ `#models/${cleanType}`,
899
+ cleanType.replace(/Model$/, ''),
900
+ `${cleanType}Model`
901
+ ];
902
+ for (const variation of variations) {
903
+ if (this.schemas[variation]?.properties) {
904
+ return {
905
+ properties: this.schemas[variation].properties,
906
+ required: this.schemas[variation].required || []
907
+ };
908
+ }
909
+ }
910
+ return { properties: {}, required: [] };
911
+ }
912
+ parseInterfaces(data) {
913
+ data = data.replace(/\t/g, "").replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, "");
914
+ let currentInterface = null;
915
+ const interfaces = {};
916
+ const interfaceDefinitions = new Map();
917
+ const lines = data.split("\n");
918
+ for (let i = 0; i < lines.length; i++) {
919
+ const line = lines[i].trim();
920
+ const isDefault = line.startsWith("export default interface");
921
+ if (line.startsWith("interface") || line.startsWith("export interface") || isDefault) {
922
+ const sp = line.split(/\s+/);
923
+ const idx = line.endsWith("}") ? sp.length - 1 : sp.length - 2;
924
+ const name = sp[idx].split(/[{\s]/)[0];
925
+ const extendedTypes = this.parseExtends(line);
926
+ interfaceDefinitions.set(name, {
927
+ extends: extendedTypes,
928
+ properties: {},
929
+ required: [],
930
+ startLine: i,
931
+ examples: {}
932
+ });
933
+ currentInterface = name;
934
+ continue;
935
+ }
936
+ if (currentInterface && line === "}") {
937
+ currentInterface = null;
938
+ continue;
939
+ }
940
+ if (currentInterface && line && !line.startsWith("//") && !line.startsWith("/*") && !line.startsWith("*")) {
941
+ const def = interfaceDefinitions.get(currentInterface);
942
+ if (def) {
943
+ const previousLine = i > 0 ? lines[i - 1].trim() : "";
944
+ const isRequired = previousLine.includes("@required");
945
+ // extract example value from comment @example(john)
946
+ const example = previousLine.match(/@example\((.*)\)/)?.[1];
947
+ const [prop, type] = line.split(":").map(s => s.trim());
948
+ if (prop && type) {
949
+ const cleanProp = prop.replace("?", "");
950
+ def.properties[cleanProp] = type.replace(";", "");
951
+ if (isRequired || !prop.includes("?")) {
952
+ def.required.push(cleanProp);
953
+ }
954
+ if (example)
955
+ def.examples[cleanProp] = example;
956
+ }
957
+ }
958
+ }
959
+ }
960
+ for (const [name, def] of interfaceDefinitions) {
961
+ let allProperties = {};
962
+ let requiredFields = new Set(def.required);
963
+ for (const baseType of def.extends) {
964
+ const baseSchema = this.schemas[baseType];
965
+ if (baseSchema) {
966
+ if (baseSchema.properties) {
967
+ Object.assign(allProperties, baseSchema.properties);
968
+ }
969
+ if (baseSchema.required) {
970
+ baseSchema.required.forEach(field => requiredFields.add(field));
971
+ }
972
+ }
973
+ }
974
+ Object.assign(allProperties, def.properties);
975
+ const parsedProperties = {};
976
+ for (const [key, value] of Object.entries(allProperties)) {
977
+ if (typeof value === 'object' && value !== null && 'type' in value) {
978
+ parsedProperties[key] = value;
979
+ }
980
+ else {
981
+ parsedProperties[key] = this.parseType(value, key);
982
+ }
983
+ if (def.examples[key]) {
984
+ parsedProperties[key].example = def.examples[key];
985
+ }
986
+ }
987
+ const schema = {
988
+ type: "object",
989
+ properties: parsedProperties,
990
+ required: Array.from(requiredFields),
991
+ description: `${name}${def.extends.length ? ` extends ${def.extends.join(", ")}` : ""} (Interface)`
992
+ };
993
+ if (schema.required.length === 0) {
994
+ delete schema.required;
995
+ }
996
+ interfaces[name] = schema;
997
+ }
998
+ return interfaces;
999
+ }
1000
+ parseExtends(line) {
1001
+ const matches = line.match(/extends\s+([^{]+)/);
1002
+ if (!matches)
1003
+ return [];
1004
+ return matches[1]
1005
+ .split(",")
1006
+ .map(type => type.trim())
1007
+ .map(type => {
1008
+ const cleanType = type.split('/').pop();
1009
+ return cleanType?.replace(/\.ts$/, '') || type;
1010
+ });
1011
+ }
1012
+ parseType(type, field) {
1013
+ if (typeof type === 'object' && type !== null && 'type' in type) {
1014
+ return type;
1015
+ }
1016
+ let isArray = false;
1017
+ if (typeof type === 'string' && type.includes("[]")) {
1018
+ type = type.replace("[]", "");
1019
+ isArray = true;
1020
+ }
1021
+ if (typeof type === 'string') {
1022
+ type = type.replace(/[;\r\n]/g, '').trim();
1023
+ }
1024
+ let prop = { type: type };
1025
+ let notRequired = field.includes("?");
1026
+ prop.nullable = notRequired;
1027
+ if (typeof type === 'string' && type.toLowerCase() === "datetime") {
1028
+ prop.type = "string";
1029
+ prop.format = "date-time";
1030
+ prop.example = "2021-03-23T16:13:08.489+01:00";
1031
+ }
1032
+ else if (typeof type === 'string' && type.toLowerCase() === "date") {
1033
+ prop.type = "string";
1034
+ prop.format = "date";
1035
+ prop.example = "2021-03-23";
1036
+ }
1037
+ else {
1038
+ const standardTypes = ["string", "number", "boolean", "integer"];
1039
+ if (typeof type === 'string' && !standardTypes.includes(type.toLowerCase())) {
1040
+ delete prop.type;
1041
+ prop.$ref = `#/components/schemas/${type}`;
1042
+ }
1043
+ else {
1044
+ if (typeof type === 'string') {
1045
+ prop.type = type.toLowerCase();
1046
+ }
1047
+ prop.example = this.exampleGenerator.exampleByType(type) ||
1048
+ this.exampleGenerator.exampleByField(field);
1049
+ }
1050
+ }
1051
+ if (isArray) {
1052
+ return {
1053
+ type: "array",
1054
+ items: prop
1055
+ };
1056
+ }
1057
+ return prop;
1058
+ }
1059
+ }
1060
+ exports.InterfaceParser = InterfaceParser;
1061
+ class EnumParser {
1062
+ constructor() { }
1063
+ parseEnums(data) {
1064
+ const enums = {};
1065
+ const lines = data.split("\n");
1066
+ let currentEnum = null;
1067
+ let description = null;
1068
+ for (const line of lines) {
1069
+ const trimmedLine = line.trim();
1070
+ if (trimmedLine.startsWith("//")) {
1071
+ description = trimmedLine.slice(2).trim();
1072
+ continue;
1073
+ }
1074
+ if (trimmedLine.startsWith("enum") ||
1075
+ trimmedLine.startsWith("export enum")) {
1076
+ const match = trimmedLine.match(/(?:export\s+)?enum\s+(\w+)/);
1077
+ if (match) {
1078
+ currentEnum = match[1];
1079
+ enums[currentEnum] = {
1080
+ type: "string",
1081
+ enum: [],
1082
+ properties: {},
1083
+ description: description || `${(0, lodash_1.startCase)(currentEnum)} enumeration`,
1084
+ };
1085
+ description = null;
1086
+ }
1087
+ continue;
1088
+ }
1089
+ if (currentEnum && trimmedLine !== "{" && trimmedLine !== "}") {
1090
+ const [key, value] = trimmedLine.split("=").map((s) => s.trim());
1091
+ if (key) {
1092
+ const enumValue = value ? this.parseEnumValue(value) : key;
1093
+ enums[currentEnum].enum.push(enumValue);
1094
+ }
1095
+ }
1096
+ if (trimmedLine === "}") {
1097
+ currentEnum = null;
1098
+ }
1099
+ }
1100
+ return enums;
1101
+ }
1102
+ parseEnumValue(value) {
1103
+ // Remove quotes and comma
1104
+ return value.replace(/['",]/g, "").trim();
1105
+ }
1106
+ }
1107
+ exports.EnumParser = EnumParser;