@midwayjs/swagger 4.0.0-beta.8 → 4.0.0
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/dist/config/config.default.js +1 -0
- package/dist/interfaces/index.d.ts +6 -0
- package/dist/swaggerExplorer.d.ts +12 -0
- package/dist/swaggerExplorer.js +344 -78
- package/dist/ui/render.js +3 -2
- package/package.json +6 -6
|
@@ -381,6 +381,12 @@ export interface SwaggerOptions {
|
|
|
381
381
|
* Weather to generate the Tag for controller
|
|
382
382
|
*/
|
|
383
383
|
isGenerateTagForController?: boolean;
|
|
384
|
+
/**
|
|
385
|
+
* Enable inferring schema metadata from @midwayjs/validation DTO rules.
|
|
386
|
+
* When enabled, inferred metadata only fills fields that are not explicitly
|
|
387
|
+
* defined by @ApiProperty.
|
|
388
|
+
*/
|
|
389
|
+
useValidationSchema?: boolean;
|
|
384
390
|
}
|
|
385
391
|
export interface MixDecoratorMetadata {
|
|
386
392
|
key: string;
|
|
@@ -15,6 +15,13 @@ export declare class SwaggerExplorer {
|
|
|
15
15
|
* 构造 router 提取方法
|
|
16
16
|
*/
|
|
17
17
|
private generateRouteMethod;
|
|
18
|
+
private upsertOperationParameter;
|
|
19
|
+
private cloneOpenAPIValue;
|
|
20
|
+
private normalizeContentSchemas;
|
|
21
|
+
private normalizeOperationParameters;
|
|
22
|
+
private normalizeOperationRequestBody;
|
|
23
|
+
private normalizeOperationResponse;
|
|
24
|
+
private normalizeOperationResponses;
|
|
18
25
|
getOperationId(controllerKey: string, webRouter: RouterOption): string;
|
|
19
26
|
/**
|
|
20
27
|
* 解析 ApiExtraModel
|
|
@@ -52,6 +59,11 @@ export declare class SwaggerExplorer {
|
|
|
52
59
|
* @param clzz
|
|
53
60
|
*/
|
|
54
61
|
protected parseClzz(clzz: Type): any;
|
|
62
|
+
private mergePropertyMetadata;
|
|
63
|
+
protected getValidationSchemaHelper(): any;
|
|
64
|
+
private inferValidationProperties;
|
|
65
|
+
private inferValidateProperties;
|
|
66
|
+
protected hasClassValidatorMetadata(clzz: Type): boolean;
|
|
55
67
|
/**
|
|
56
68
|
* 授权验证
|
|
57
69
|
* @param opts
|
package/dist/swaggerExplorer.js
CHANGED
|
@@ -15,6 +15,8 @@ const constants_1 = require("./constants");
|
|
|
15
15
|
const documentBuilder_1 = require("./documentBuilder");
|
|
16
16
|
const _1 = require(".");
|
|
17
17
|
const enum_utils_1 = require("./common/enum.utils");
|
|
18
|
+
const VALIDATION_RULES_KEY = 'validation:rules';
|
|
19
|
+
const VALIDATE_RULES_KEY = 'common:rules';
|
|
18
20
|
let SwaggerExplorer = class SwaggerExplorer {
|
|
19
21
|
swaggerConfig = {};
|
|
20
22
|
documentBuilder = new documentBuilder_1.DocumentBuilder();
|
|
@@ -38,14 +40,15 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
38
40
|
typeof this.swaggerConfig?.externalDocs === 'object') {
|
|
39
41
|
this.documentBuilder.setExternalDoc(this.swaggerConfig?.externalDocs?.description, this.swaggerConfig?.externalDocs?.url);
|
|
40
42
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
for (const serv of
|
|
43
|
+
const servers = this.swaggerConfig?.servers;
|
|
44
|
+
if (Array.isArray(servers)) {
|
|
45
|
+
for (const serv of servers) {
|
|
44
46
|
this.documentBuilder.addServer(serv?.url, serv?.description, serv?.variables);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
const tags = this.swaggerConfig?.tags;
|
|
50
|
+
if (Array.isArray(tags)) {
|
|
51
|
+
for (const t of tags) {
|
|
49
52
|
this.documentBuilder.addTag(t.name, t.description, t.externalDocs);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -54,13 +57,14 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
54
57
|
this.swaggerConfig.documentOptions.operationIdFactory;
|
|
55
58
|
}
|
|
56
59
|
// 设置 auth 类型
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
const authConfig = this.swaggerConfig?.auth;
|
|
61
|
+
if (Array.isArray(authConfig)) {
|
|
62
|
+
for (const a of authConfig) {
|
|
59
63
|
this.setAuth(a);
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
|
-
else {
|
|
63
|
-
this.setAuth(
|
|
66
|
+
else if (authConfig) {
|
|
67
|
+
this.setAuth(authConfig);
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
addGlobalPrefix(globalPrefix) {
|
|
@@ -264,13 +268,19 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
264
268
|
if (!opts) {
|
|
265
269
|
opts = {};
|
|
266
270
|
}
|
|
267
|
-
const
|
|
271
|
+
const operationParameters = this.normalizeOperationParameters(operMeta?.metadata?.parameters);
|
|
272
|
+
const operationRequestBody = this.normalizeOperationRequestBody(operMeta?.metadata?.requestBody);
|
|
273
|
+
const operationResponses = this.normalizeOperationResponses(operMeta?.metadata?.responses);
|
|
274
|
+
const parameters = [...operationParameters];
|
|
275
|
+
let requestBodyFromOperation = !!operationRequestBody;
|
|
268
276
|
opts[webRouter.requestMethod] = {
|
|
269
277
|
summary: getNotEmptyValue(operMeta?.metadata?.summary, webRouter.summary),
|
|
270
278
|
description: getNotEmptyValue(operMeta?.metadata?.description, webRouter.description),
|
|
271
279
|
operationId: operMeta?.metadata?.operationId ||
|
|
272
280
|
this.getOperationId(target.name, webRouter),
|
|
273
|
-
tags: routerTags.length ? routerTags : operMeta?.metadata?.tags ?? [],
|
|
281
|
+
tags: routerTags.length ? routerTags : (operMeta?.metadata?.tags ?? []),
|
|
282
|
+
...(operationRequestBody ? { requestBody: operationRequestBody } : {}),
|
|
283
|
+
...(operationResponses ? { responses: operationResponses } : {}),
|
|
274
284
|
};
|
|
275
285
|
if (operMeta?.metadata?.deprecated != null) {
|
|
276
286
|
opts[webRouter.requestMethod].deprecated =
|
|
@@ -316,7 +326,7 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
316
326
|
const p = param.metadata;
|
|
317
327
|
p.schema = this.formatType(param.metadata.schema);
|
|
318
328
|
if (p.in === 'query' || p.in === 'path' || p.in === 'header') {
|
|
319
|
-
|
|
329
|
+
this.upsertOperationParameter(parameters, p);
|
|
320
330
|
}
|
|
321
331
|
else if (p.in === 'body') {
|
|
322
332
|
p.content = p.content ?? {};
|
|
@@ -332,13 +342,28 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
332
342
|
// if requestBody is already set, skip
|
|
333
343
|
opts[webRouter.requestMethod].requestBody =
|
|
334
344
|
opts[webRouter.requestMethod].requestBody ?? {};
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
if (requestBodyFromOperation) {
|
|
346
|
+
if (p.description !== undefined) {
|
|
347
|
+
opts[webRouter.requestMethod].requestBody.description =
|
|
348
|
+
p.description;
|
|
349
|
+
}
|
|
350
|
+
if (p.content !== undefined) {
|
|
351
|
+
opts[webRouter.requestMethod].requestBody.content = p.content;
|
|
352
|
+
}
|
|
353
|
+
if (p.required !== undefined) {
|
|
354
|
+
opts[webRouter.requestMethod].requestBody.required = p.required;
|
|
355
|
+
}
|
|
356
|
+
requestBodyFromOperation = false;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
opts[webRouter.requestMethod].requestBody.description =
|
|
360
|
+
opts[webRouter.requestMethod].requestBody.description ??
|
|
361
|
+
p.description;
|
|
362
|
+
opts[webRouter.requestMethod].requestBody.content =
|
|
363
|
+
opts[webRouter.requestMethod].requestBody.content ?? p.content;
|
|
364
|
+
opts[webRouter.requestMethod].requestBody.required =
|
|
365
|
+
opts[webRouter.requestMethod].requestBody.required ?? p.required;
|
|
366
|
+
}
|
|
342
367
|
}
|
|
343
368
|
}
|
|
344
369
|
for (const arg of args) {
|
|
@@ -377,7 +402,7 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
377
402
|
schema: schema.properties[pName],
|
|
378
403
|
required: schema.required?.includes(pName) || false,
|
|
379
404
|
};
|
|
380
|
-
|
|
405
|
+
this.upsertOperationParameter(parameters, pp);
|
|
381
406
|
});
|
|
382
407
|
continue;
|
|
383
408
|
}
|
|
@@ -461,76 +486,23 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
461
486
|
// in body 不需要处理
|
|
462
487
|
continue;
|
|
463
488
|
}
|
|
464
|
-
|
|
489
|
+
this.upsertOperationParameter(parameters, p);
|
|
465
490
|
}
|
|
466
491
|
// class header 需要使用 ApiHeader 装饰器
|
|
467
492
|
if (headers && headers.length) {
|
|
468
|
-
headers.forEach(header =>
|
|
493
|
+
headers.forEach(header => this.upsertOperationParameter(parameters, header, true));
|
|
469
494
|
}
|
|
470
495
|
// 获取方法上的 @ApiHeader
|
|
471
496
|
const methodHeaders = metaForMethods.filter(item => item.key === constants_1.DECORATORS.API_HEADERS);
|
|
472
497
|
if (methodHeaders.length > 0) {
|
|
473
|
-
methodHeaders.forEach(item =>
|
|
498
|
+
methodHeaders.forEach(item => this.upsertOperationParameter(parameters, item.metadata, true));
|
|
474
499
|
}
|
|
475
500
|
opts[webRouter.requestMethod].parameters = parameters;
|
|
476
501
|
const responses = metaForMethods.filter(item => item.key === constants_1.DECORATORS.API_RESPONSE &&
|
|
477
502
|
item.propertyName === webRouter.method);
|
|
478
|
-
const returnResponses = {};
|
|
503
|
+
const returnResponses = operationResponses ?? {};
|
|
479
504
|
for (const r of responses) {
|
|
480
|
-
const resp = r.metadata;
|
|
481
|
-
const keys = Object.keys(resp);
|
|
482
|
-
for (const k of keys) {
|
|
483
|
-
// 这里是引用,赋值可以直接更改
|
|
484
|
-
const tt = resp[k];
|
|
485
|
-
if (tt.schema) {
|
|
486
|
-
// response 的 schema 需要包含在 content 内
|
|
487
|
-
tt.content = {
|
|
488
|
-
'application/json': {
|
|
489
|
-
schema: this.formatType(tt.schema),
|
|
490
|
-
},
|
|
491
|
-
};
|
|
492
|
-
delete tt.schema;
|
|
493
|
-
}
|
|
494
|
-
else if (tt.type) {
|
|
495
|
-
if (core_1.Types.isClass(tt.type)) {
|
|
496
|
-
this.parseClzz(tt.type);
|
|
497
|
-
if (tt.isArray) {
|
|
498
|
-
tt.content = {
|
|
499
|
-
'application/json': {
|
|
500
|
-
schema: {
|
|
501
|
-
type: 'array',
|
|
502
|
-
items: {
|
|
503
|
-
$ref: '#/components/schemas/' + tt.type.name,
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
},
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
tt.content = {
|
|
511
|
-
'application/json': {
|
|
512
|
-
schema: {
|
|
513
|
-
$ref: '#/components/schemas/' + tt.type.name,
|
|
514
|
-
},
|
|
515
|
-
},
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
else {
|
|
520
|
-
tt.content = {
|
|
521
|
-
'text/plain': {
|
|
522
|
-
schema: {
|
|
523
|
-
type: convertSchemaType(tt.type),
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
delete tt.status;
|
|
530
|
-
delete tt.type;
|
|
531
|
-
delete tt.isArray;
|
|
532
|
-
delete tt.format;
|
|
533
|
-
}
|
|
505
|
+
const resp = this.normalizeOperationResponses(r.metadata);
|
|
534
506
|
Object.assign(returnResponses, resp);
|
|
535
507
|
}
|
|
536
508
|
if (Object.keys(returnResponses).length > 0) {
|
|
@@ -545,6 +517,133 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
545
517
|
}
|
|
546
518
|
paths[url] = opts;
|
|
547
519
|
}
|
|
520
|
+
upsertOperationParameter(parameters, parameter, prepend = false) {
|
|
521
|
+
const index = parameters.findIndex(item => {
|
|
522
|
+
return item?.name === parameter?.name && item?.in === parameter?.in;
|
|
523
|
+
});
|
|
524
|
+
if (index >= 0) {
|
|
525
|
+
parameters.splice(index, 1);
|
|
526
|
+
}
|
|
527
|
+
if (prepend) {
|
|
528
|
+
parameters.unshift(parameter);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
parameters.push(parameter);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
cloneOpenAPIValue(value) {
|
|
535
|
+
if (Array.isArray(value)) {
|
|
536
|
+
return value.map(item => this.cloneOpenAPIValue(item));
|
|
537
|
+
}
|
|
538
|
+
if (value && typeof value === 'object') {
|
|
539
|
+
const cloned = {};
|
|
540
|
+
for (const key in value) {
|
|
541
|
+
cloned[key] = this.cloneOpenAPIValue(value[key]);
|
|
542
|
+
}
|
|
543
|
+
return cloned;
|
|
544
|
+
}
|
|
545
|
+
return value;
|
|
546
|
+
}
|
|
547
|
+
normalizeContentSchemas(content) {
|
|
548
|
+
if (!content) {
|
|
549
|
+
return content;
|
|
550
|
+
}
|
|
551
|
+
for (const key in content) {
|
|
552
|
+
if (content[key]?.schema) {
|
|
553
|
+
content[key].schema = this.formatType(content[key].schema);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return content;
|
|
557
|
+
}
|
|
558
|
+
normalizeOperationParameters(parameters) {
|
|
559
|
+
if (!Array.isArray(parameters)) {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
return parameters.map(parameter => {
|
|
563
|
+
const normalized = this.cloneOpenAPIValue(parameter);
|
|
564
|
+
if (normalized?.schema) {
|
|
565
|
+
normalized.schema = this.formatType(normalized.schema);
|
|
566
|
+
}
|
|
567
|
+
if (normalized.content) {
|
|
568
|
+
normalized.content = this.normalizeContentSchemas(normalized.content);
|
|
569
|
+
}
|
|
570
|
+
return normalized;
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
normalizeOperationRequestBody(requestBody) {
|
|
574
|
+
if (!requestBody) {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
const normalized = this.cloneOpenAPIValue(requestBody);
|
|
578
|
+
if (normalized.content) {
|
|
579
|
+
normalized.content = this.normalizeContentSchemas(normalized.content);
|
|
580
|
+
}
|
|
581
|
+
return normalized;
|
|
582
|
+
}
|
|
583
|
+
normalizeOperationResponse(response) {
|
|
584
|
+
const normalized = this.cloneOpenAPIValue(response);
|
|
585
|
+
if (normalized.schema) {
|
|
586
|
+
normalized.content = {
|
|
587
|
+
'application/json': {
|
|
588
|
+
schema: this.formatType(normalized.schema),
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
delete normalized.schema;
|
|
592
|
+
}
|
|
593
|
+
else if (normalized.type) {
|
|
594
|
+
if (core_1.Types.isClass(normalized.type)) {
|
|
595
|
+
this.parseClzz(normalized.type);
|
|
596
|
+
if (normalized.isArray) {
|
|
597
|
+
normalized.content = {
|
|
598
|
+
'application/json': {
|
|
599
|
+
schema: {
|
|
600
|
+
type: 'array',
|
|
601
|
+
items: {
|
|
602
|
+
$ref: '#/components/schemas/' + normalized.type.name,
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
normalized.content = {
|
|
610
|
+
'application/json': {
|
|
611
|
+
schema: {
|
|
612
|
+
$ref: '#/components/schemas/' + normalized.type.name,
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
normalized.content = {
|
|
620
|
+
'text/plain': {
|
|
621
|
+
schema: {
|
|
622
|
+
type: convertSchemaType(normalized.type),
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else if (normalized.content) {
|
|
629
|
+
normalized.content = this.normalizeContentSchemas(normalized.content);
|
|
630
|
+
}
|
|
631
|
+
delete normalized.status;
|
|
632
|
+
delete normalized.type;
|
|
633
|
+
delete normalized.isArray;
|
|
634
|
+
delete normalized.format;
|
|
635
|
+
return normalized;
|
|
636
|
+
}
|
|
637
|
+
normalizeOperationResponses(responses) {
|
|
638
|
+
if (!responses) {
|
|
639
|
+
return undefined;
|
|
640
|
+
}
|
|
641
|
+
const normalizedResponses = this.cloneOpenAPIValue(responses);
|
|
642
|
+
for (const status in normalizedResponses) {
|
|
643
|
+
normalizedResponses[status] = this.normalizeOperationResponse(normalizedResponses[status]);
|
|
644
|
+
}
|
|
645
|
+
return normalizedResponses;
|
|
646
|
+
}
|
|
548
647
|
getOperationId(controllerKey, webRouter) {
|
|
549
648
|
return this.operationIdFactory(controllerKey, webRouter);
|
|
550
649
|
}
|
|
@@ -772,6 +871,18 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
772
871
|
for (const key in props) {
|
|
773
872
|
props[key] = props[key][props[key].length - 1];
|
|
774
873
|
}
|
|
874
|
+
if (this.swaggerConfig?.useValidationSchema) {
|
|
875
|
+
const inferredProps = this.inferValidationProperties(clzz);
|
|
876
|
+
for (const key of Object.keys(inferredProps)) {
|
|
877
|
+
if (!props[key]) {
|
|
878
|
+
props[key] = inferredProps[key];
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
const existingMeta = props[key].metadata || {};
|
|
882
|
+
const inferredMeta = inferredProps[key].metadata || {};
|
|
883
|
+
props[key].metadata = this.mergePropertyMetadata(existingMeta, inferredMeta);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
775
886
|
const tt = {
|
|
776
887
|
type: 'object',
|
|
777
888
|
properties: {},
|
|
@@ -843,6 +954,120 @@ let SwaggerExplorer = class SwaggerExplorer {
|
|
|
843
954
|
// just for test
|
|
844
955
|
return tt;
|
|
845
956
|
}
|
|
957
|
+
mergePropertyMetadata(swaggerMetadata, inferredMetadata) {
|
|
958
|
+
const mergedMetadata = { ...swaggerMetadata };
|
|
959
|
+
const fillableKeys = [
|
|
960
|
+
'type',
|
|
961
|
+
'items',
|
|
962
|
+
'format',
|
|
963
|
+
'enum',
|
|
964
|
+
'$ref',
|
|
965
|
+
'pattern',
|
|
966
|
+
'default',
|
|
967
|
+
];
|
|
968
|
+
for (const key of fillableKeys) {
|
|
969
|
+
if (mergedMetadata[key] === undefined &&
|
|
970
|
+
inferredMetadata[key] !== undefined) {
|
|
971
|
+
mergedMetadata[key] = inferredMetadata[key];
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (mergedMetadata.required === undefined &&
|
|
975
|
+
inferredMetadata.required !== undefined) {
|
|
976
|
+
mergedMetadata.required = inferredMetadata.required;
|
|
977
|
+
}
|
|
978
|
+
return mergedMetadata;
|
|
979
|
+
}
|
|
980
|
+
getValidationSchemaHelper() {
|
|
981
|
+
try {
|
|
982
|
+
const validationPkg = (0, core_1.safeRequire)('@midwayjs/validation');
|
|
983
|
+
const registry = validationPkg?.registry;
|
|
984
|
+
if (!registry?.getDefaultValidator) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
return registry.getDefaultValidator()?.schemaHelper;
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
// When @midwayjs/validate and @midwayjs/validation are both installed in
|
|
991
|
+
// the same process, loading validation package may throw duplicated error
|
|
992
|
+
// group exceptions. Swagger should degrade gracefully in this case.
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
inferValidationProperties(clzz) {
|
|
997
|
+
const ruleProps = core_1.MetadataManager.getPropertiesWithMetadata(VALIDATION_RULES_KEY, clzz) ||
|
|
998
|
+
{};
|
|
999
|
+
const hasRuleMetadata = Object.keys(ruleProps).length > 0;
|
|
1000
|
+
const validateRuleProps = core_1.MetadataManager.getPropertiesWithMetadata(VALIDATE_RULES_KEY, clzz) || {};
|
|
1001
|
+
const hasValidateRuleMetadata = Object.keys(validateRuleProps).length > 0;
|
|
1002
|
+
const hasClassValidatorMetadata = this.hasClassValidatorMetadata(clzz);
|
|
1003
|
+
if (!hasRuleMetadata &&
|
|
1004
|
+
!hasClassValidatorMetadata &&
|
|
1005
|
+
!hasValidateRuleMetadata) {
|
|
1006
|
+
return {};
|
|
1007
|
+
}
|
|
1008
|
+
const inferredProps = {};
|
|
1009
|
+
if (hasRuleMetadata || hasClassValidatorMetadata) {
|
|
1010
|
+
const schemaHelper = this.getValidationSchemaHelper();
|
|
1011
|
+
if (schemaHelper &&
|
|
1012
|
+
typeof schemaHelper.getSwaggerPropertyKeys === 'function' &&
|
|
1013
|
+
typeof schemaHelper.getSwaggerPropertyMetadata === 'function') {
|
|
1014
|
+
const propertyKeys = schemaHelper.getSwaggerPropertyKeys(clzz) || [];
|
|
1015
|
+
for (const key of propertyKeys) {
|
|
1016
|
+
const metadata = schemaHelper.getSwaggerPropertyMetadata(clzz, key);
|
|
1017
|
+
if (metadata) {
|
|
1018
|
+
inferredProps[key] = {
|
|
1019
|
+
metadata,
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (hasValidateRuleMetadata) {
|
|
1026
|
+
const validateInferredProps = this.inferValidateProperties(clzz);
|
|
1027
|
+
for (const [key, value] of Object.entries(validateInferredProps)) {
|
|
1028
|
+
if (!inferredProps[key]) {
|
|
1029
|
+
inferredProps[key] = value;
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
const mergedMetadata = this.mergePropertyMetadata(inferredProps[key].metadata || {}, value.metadata || {});
|
|
1033
|
+
inferredProps[key] = {
|
|
1034
|
+
metadata: mergedMetadata,
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return inferredProps;
|
|
1039
|
+
}
|
|
1040
|
+
inferValidateProperties(clzz) {
|
|
1041
|
+
const inferredProps = {};
|
|
1042
|
+
const ruleProps = core_1.MetadataManager.getPropertiesWithMetadata(VALIDATE_RULES_KEY, clzz) || {};
|
|
1043
|
+
for (const key of Object.keys(ruleProps)) {
|
|
1044
|
+
let schema = ruleProps[key];
|
|
1045
|
+
if (typeof schema === 'function') {
|
|
1046
|
+
schema = schema();
|
|
1047
|
+
}
|
|
1048
|
+
const metadata = inferJoiPropertyMetadata(schema);
|
|
1049
|
+
if (metadata) {
|
|
1050
|
+
inferredProps[key] = {
|
|
1051
|
+
metadata,
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return inferredProps;
|
|
1056
|
+
}
|
|
1057
|
+
hasClassValidatorMetadata(clzz) {
|
|
1058
|
+
try {
|
|
1059
|
+
const classValidator = (0, core_1.safeRequire)('class-validator-multi-lang-lite');
|
|
1060
|
+
const storage = classValidator?.getMetadataStorage?.();
|
|
1061
|
+
if (!storage?.getTargetValidationMetadatas) {
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
const metadatas = storage.getTargetValidationMetadatas(clzz, '', false, false) || [];
|
|
1065
|
+
return metadatas.length > 0;
|
|
1066
|
+
}
|
|
1067
|
+
catch {
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
846
1071
|
/**
|
|
847
1072
|
* 授权验证
|
|
848
1073
|
* @param opts
|
|
@@ -960,6 +1185,47 @@ function convertSchemaType(value) {
|
|
|
960
1185
|
return 'object';
|
|
961
1186
|
}
|
|
962
1187
|
}
|
|
1188
|
+
function inferJoiPropertyMetadata(schema) {
|
|
1189
|
+
if (!schema || typeof schema.describe !== 'function') {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
const desc = schema.describe();
|
|
1193
|
+
if (!desc || typeof desc !== 'object') {
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
const typeMap = {
|
|
1197
|
+
string: 'string',
|
|
1198
|
+
number: 'number',
|
|
1199
|
+
boolean: 'boolean',
|
|
1200
|
+
array: 'array',
|
|
1201
|
+
date: 'string',
|
|
1202
|
+
object: 'object',
|
|
1203
|
+
};
|
|
1204
|
+
const metadata = {
|
|
1205
|
+
type: typeMap[desc.type] || 'object',
|
|
1206
|
+
};
|
|
1207
|
+
if (desc?.flags?.presence === 'required') {
|
|
1208
|
+
metadata.required = true;
|
|
1209
|
+
}
|
|
1210
|
+
else if (desc?.flags?.presence === 'optional' || !desc?.flags?.presence) {
|
|
1211
|
+
metadata.required = false;
|
|
1212
|
+
}
|
|
1213
|
+
if (desc.type === 'array' && Array.isArray(desc.items)) {
|
|
1214
|
+
metadata.items = {
|
|
1215
|
+
type: typeMap[desc.items[0]?.type] || 'object',
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
if (desc.type === 'date') {
|
|
1219
|
+
metadata.format = 'date-time';
|
|
1220
|
+
}
|
|
1221
|
+
if (Array.isArray(desc.allow)) {
|
|
1222
|
+
const enumValues = desc.allow.filter(item => item !== '' && item !== null && item !== undefined);
|
|
1223
|
+
if (enumValues.length > 0) {
|
|
1224
|
+
metadata.enum = enumValues;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return metadata;
|
|
1228
|
+
}
|
|
963
1229
|
function getNotEmptyValue(...args) {
|
|
964
1230
|
for (const arg of args) {
|
|
965
1231
|
if (arg) {
|
package/dist/ui/render.js
CHANGED
|
@@ -23,8 +23,9 @@ function renderSwaggerUIDist(swaggerConfig, swaggerExplorer) {
|
|
|
23
23
|
function replaceInfo(content) {
|
|
24
24
|
let str = `location.href.replace('${swaggerConfig.swaggerPath}/index.html', '${swaggerConfig.swaggerPath}/index.json'),\n validatorUrl: null,`;
|
|
25
25
|
if (swaggerConfig.displayOptions) {
|
|
26
|
-
Object.
|
|
27
|
-
|
|
26
|
+
Object.entries(swaggerConfig.displayOptions).forEach(([key, value]) => {
|
|
27
|
+
const variable = typeof value === 'string' ? `'${value}'` : value;
|
|
28
|
+
str += `\n${key}: ${variable},`;
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
return content.replace('"https://petstore.swagger.io/v2/swagger.json",', str);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midwayjs/swagger",
|
|
3
|
-
"version": "4.0.0
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Midway Component for Swagger API Documentation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
],
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@apidevtools/swagger-parser": "11.0.1",
|
|
15
|
-
"@midwayjs/core": "^4.0.0
|
|
16
|
-
"@midwayjs/koa": "^4.0.0
|
|
17
|
-
"@midwayjs/mock": "^4.0.0
|
|
18
|
-
"@midwayjs/validate": "^4.0.0
|
|
15
|
+
"@midwayjs/core": "^4.0.0",
|
|
16
|
+
"@midwayjs/koa": "^4.0.0",
|
|
17
|
+
"@midwayjs/mock": "^4.0.0",
|
|
18
|
+
"@midwayjs/validate": "^4.0.0",
|
|
19
19
|
"swagger-ui-dist": "5.18.3"
|
|
20
20
|
},
|
|
21
21
|
"author": "Kurten Chan <chinkurten@gmail.com>",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"type": "git",
|
|
31
31
|
"url": "https://github.com/midwayjs/midway.git"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "014f32c23ebc1d5ac21777c76be2fd373ce992d8"
|
|
34
34
|
}
|