yapi-apifox-to-typescript 4.2.2 → 5.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.
@@ -1,560 +1,310 @@
1
- import _createForOfIteratorHelperLoose from "@babel/runtime/helpers/esm/createForOfIteratorHelperLoose";
2
- import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
3
- import _extends from "@babel/runtime/helpers/esm/extends";
4
- import _regeneratorRuntime from "@babel/runtime/regenerator";
5
- import axios from 'axios';
6
- import { RequestBodyType, RequestFormItemType, RequestParamType, RequestQueryType, Required, ResponseBodyType } from './types';
7
- import { getFilteredCat } from './utils';
8
- export var ApifoxToYApiData = /*#__PURE__*/function () {
9
- function ApifoxToYApiData(options) {
10
- this.openApiDoc = null;
11
- this.options = void 0;
12
- this.project = {
13
- _id: 0,
14
- _url: '',
15
- name: 'ApifoxProject',
16
- desc: '',
17
- basepath: '',
18
- tag: [],
19
- env: [{
20
- name: '',
21
- domain: ''
22
- }]
23
- };
24
- this.schemas = {};
25
- this.interfaceIdCounter = 0;
26
- this.options = options;
27
- }
28
-
29
- var _proto = ApifoxToYApiData.prototype;
30
-
31
- _proto.resolveRef = function resolveRef(ref) {
32
- if (!ref.startsWith('#/components/schemas/')) {
33
- return null;
34
- }
35
-
36
- var schemaName = ref.replace('#/components/schemas/', '');
37
- return this.schemas[schemaName] || null;
38
- };
39
-
40
- _proto.resolveSchema = function resolveSchema(schema) {
41
- if (!schema) return undefined;
42
-
43
- if (schema.$ref) {
44
- return this.resolveRef(schema.$ref) || schema;
45
- }
46
-
47
- return schema;
48
- };
49
-
50
- _proto.getSchemaType = function getSchemaType(schema) {
51
- if (!schema) return 'string';
52
- var resolved = this.resolveSchema(schema);
53
- if (!resolved) return 'string';
54
- return resolved.type || 'string';
55
- };
56
-
57
- _proto.getRequestParamType = function getRequestParamType(schema) {
58
- var schemaType = this.getSchemaType(schema);
59
- if (schemaType === 'boolean') return RequestParamType.boolean;
60
- if (schemaType === 'number') return RequestParamType.number;
61
- return RequestParamType.string;
62
- };
63
-
64
- _proto.getRequestQueryType = function getRequestQueryType(schema) {
65
- var schemaType = this.getSchemaType(schema);
66
- if (schemaType === 'boolean') return RequestQueryType.boolean;
67
- if (schemaType === 'number') return RequestQueryType.number;
68
- return RequestQueryType.string;
69
- };
70
-
71
- _proto.convertParametersToReqParams = function convertParametersToReqParams(parameters, type) {
72
- var _this = this;
73
-
74
- if (parameters === void 0) {
75
- parameters = [];
76
- }
77
-
78
- if (type === 'path') {
79
- return parameters.filter(function (p) {
80
- return p.in === 'path';
81
- }).map(function (p) {
82
- return {
83
- name: p.name,
84
- desc: p.description || '',
85
- example: p.example || '',
86
- required: p.required ? Required.true : Required.false,
87
- type: _this.getRequestParamType(p.schema)
88
- };
89
- });
90
- }
91
-
92
- return parameters.filter(function (p) {
93
- return p.in === 'query';
94
- }).map(function (p) {
95
- return {
96
- name: p.name,
97
- desc: p.description || '',
98
- example: p.example || '',
99
- required: p.required ? Required.true : Required.false,
100
- type: _this.getRequestQueryType(p.schema)
101
- };
102
- });
103
- };
104
-
105
- _proto.convertRequestBody = function convertRequestBody(requestBody) {
106
- if (!(requestBody != null && requestBody.content)) {
107
- return {
108
- req_body_type: RequestBodyType.none,
109
- req_body_is_json_schema: false,
110
- req_body_form: [],
111
- req_body_other: '',
112
- contentType: ''
113
- };
114
- }
115
-
116
- var contentTypes = Object.keys(requestBody.content);
117
- var jsonContentType = contentTypes.find(function (ct) {
118
- return ct.includes('application/json');
119
- });
120
- var formContentType = contentTypes.find(function (ct) {
121
- return ct.includes('application/x-www-form-urlencoded') || ct.includes('multipart/form-data');
122
- });
123
-
124
- if (jsonContentType) {
125
- var schema = requestBody.content[jsonContentType].schema;
126
- return {
127
- req_body_type: RequestBodyType.json,
128
- req_body_is_json_schema: true,
129
- req_body_form: [],
130
- req_body_other: schema ? JSON.stringify(this.normalizeSchema(schema)) : '',
131
- contentType: jsonContentType
132
- };
133
- }
134
-
135
- if (formContentType) {
136
- var _schema = requestBody.content[formContentType].schema;
137
- var resolvedSchema = this.resolveSchema(_schema);
138
- var formItems = [];
139
-
140
- if (resolvedSchema != null && resolvedSchema.properties) {
141
- var requiredFields = resolvedSchema.required || [];
142
-
143
- for (var _i = 0, _Object$entries = Object.entries(resolvedSchema.properties); _i < _Object$entries.length; _i++) {
144
- var _Object$entries$_i = _Object$entries[_i],
145
- name = _Object$entries$_i[0],
146
- prop = _Object$entries$_i[1];
147
- var resolvedProp = this.resolveSchema(prop);
148
- formItems.push({
149
- name: name,
150
- type: (resolvedProp == null ? void 0 : resolvedProp.type) === 'file' || (resolvedProp == null ? void 0 : resolvedProp.format) === 'binary' ? RequestFormItemType.file : RequestFormItemType.text,
151
- desc: (resolvedProp == null ? void 0 : resolvedProp.description) || '',
152
- example: (resolvedProp == null ? void 0 : resolvedProp.example) || '',
153
- required: requiredFields.includes(name) ? Required.true : Required.false
154
- });
155
- }
156
- }
157
-
158
- return {
159
- req_body_type: RequestBodyType.form,
160
- req_body_is_json_schema: false,
161
- req_body_form: formItems,
162
- req_body_other: '',
163
- contentType: formContentType
164
- };
165
- }
166
-
167
- return {
168
- req_body_type: RequestBodyType.raw,
169
- req_body_is_json_schema: false,
170
- req_body_form: [],
171
- req_body_other: '',
172
- contentType: contentTypes[0] || ''
173
- };
174
- };
175
-
176
- _proto.normalizeSchema = function normalizeSchema(schema) {
177
- var result = _extends({}, schema);
178
-
179
- if (schema.$ref) {
180
- var resolved = this.resolveRef(schema.$ref);
181
-
182
- if (resolved) {
183
- return this.normalizeSchema(resolved);
184
- }
185
- }
186
-
187
- if (schema.properties) {
188
- result.properties = {};
189
-
190
- for (var _i2 = 0, _Object$entries2 = Object.entries(schema.properties); _i2 < _Object$entries2.length; _i2++) {
191
- var _Object$entries2$_i = _Object$entries2[_i2],
192
- key = _Object$entries2$_i[0],
193
- _value = _Object$entries2$_i[1];
194
-
195
- if (_value) {
196
- result.properties[key] = this.normalizeSchema(_value);
197
- }
198
- }
199
- }
200
-
201
- if (schema.items) {
202
- result.items = this.normalizeSchema(schema.items);
203
- }
204
-
205
- delete result.$ref;
206
- return result;
207
- };
208
-
209
- _proto.convertResponse = function convertResponse(responses) {
210
- if (responses === void 0) {
211
- responses = {};
212
- }
213
-
214
- var successResponse = responses['200'] || responses['201'] || responses['204'];
215
-
216
- if (!(successResponse != null && successResponse.content)) {
217
- return {
218
- res_body_type: ResponseBodyType.json,
219
- res_body_is_json_schema: false,
220
- res_body: ''
221
- };
222
- }
223
-
224
- var jsonContent = successResponse.content['application/json'];
225
-
226
- if (jsonContent != null && jsonContent.schema) {
227
- return {
228
- res_body_type: ResponseBodyType.json,
229
- res_body_is_json_schema: true,
230
- res_body: JSON.stringify(this.normalizeSchema(jsonContent.schema))
231
- };
232
- }
233
-
234
- return {
235
- res_body_type: ResponseBodyType.json,
236
- res_body_is_json_schema: false,
237
- res_body: ''
238
- };
239
- };
240
-
241
- _proto.generateInterfaceId = function generateInterfaceId() {
242
- this.interfaceIdCounter += 1;
243
- return this.interfaceIdCounter;
244
- }
245
- /**
246
- * 获取接口所属的目录名称
247
- * Apifox 优先使用 x-apifox-folder 字段,其次才用 tags[0]
248
- */
249
- ;
250
-
251
- _proto.getOperationFolder = function getOperationFolder(operation) {
252
- // 优先使用 x-apifox-folder
253
- if (operation['x-apifox-folder']) {
254
- return operation['x-apifox-folder'];
255
- } // 其次使用 tags[0]
256
-
257
-
258
- if (operation.tags && operation.tags.length > 0) {
259
- return operation.tags[0];
260
- }
261
-
262
- return 'default';
263
- };
264
-
265
- _proto.convertOperationToInterface = function convertOperationToInterface(path, method, operation, categoryName, categoryId) {
266
- var _operation$tags;
267
-
268
- var parameters = operation.parameters || [];
269
- var pathParams = this.convertParametersToReqParams(parameters, 'path');
270
- var queryParams = this.convertParametersToReqParams(parameters, 'query'); // const headerParams = parameters.filter(p => p.in === 'header')
271
-
272
- var reqHeaders = [];
273
- var requestBodyInfo = this.convertRequestBody(operation.requestBody);
274
-
275
- if (requestBodyInfo.contentType) {
276
- reqHeaders.push({
277
- name: 'Content-Type',
278
- value: requestBodyInfo.contentType,
279
- desc: '',
280
- example: '',
281
- required: Required.true
282
- });
283
- }
284
-
285
- var responseInfo = this.convertResponse(operation.responses); // 获取接口所属目录:优先使用 x-apifox-folder,其次用 tags
286
-
287
- var folderName = this.getOperationFolder(operation);
288
- var interfaceTags = (_operation$tags = operation.tags) != null && _operation$tags.length ? operation.tags : folderName !== 'default' ? [folderName] : [];
289
- return {
290
- _id: this.generateInterfaceId(),
291
- _category: {
292
- _id: categoryId,
293
- _url: '',
294
- name: categoryName,
295
- desc: '',
296
- add_time: 0,
297
- up_time: 0
298
- },
299
- _project: _extends({}, this.project),
300
- _url: '',
301
- title: operation.summary || path,
302
- status: operation.deprecated ? 'undone' : 'done',
303
- markdown: operation.description || '',
304
- path: path,
305
- method: method.toUpperCase(),
306
- project_id: this.options.projectId,
307
- catid: categoryId,
308
- tag: interfaceTags,
309
- req_headers: reqHeaders,
310
- req_params: pathParams,
311
- req_query: queryParams,
312
- req_body_type: requestBodyInfo.req_body_type,
313
- req_body_is_json_schema: requestBodyInfo.req_body_is_json_schema,
314
- req_body_form: requestBodyInfo.req_body_form,
315
- req_body_other: requestBodyInfo.req_body_other,
316
- res_body_type: responseInfo.res_body_type,
317
- res_body_is_json_schema: responseInfo.res_body_is_json_schema,
318
- res_body: responseInfo.res_body,
319
- add_time: Math.floor(Date.now() / 1000),
320
- up_time: Math.floor(Date.now() / 1000),
321
- uid: 0
322
- };
323
- };
324
-
325
- _proto.getProjectInfo = /*#__PURE__*/function () {
326
- var _getProjectInfo = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
327
- var _this$options$addFold, _this$options$oasVers, _this$options$exportF, _this$openApiDoc, _this$openApiDoc$comp, _this$openApiDoc2, _this$openApiDoc2$inf, _this$openApiDoc3, _this$openApiDoc3$inf;
328
-
329
- var baseUrl, response;
330
- return _regeneratorRuntime.wrap(function _callee$(_context) {
331
- while (1) {
332
- switch (_context.prev = _context.next) {
333
- case 0:
334
- baseUrl = this.options.serverUrl.replace(/\/$/, '');
335
- _context.next = 3;
336
- return axios.post(baseUrl + "/v1/projects/" + this.options.projectId + "/export-openapi?locale=zh-CN", {
337
- scope: {
338
- type: 'ALL',
339
- excludedByTags: []
340
- },
341
- options: {
342
- includeApifoxExtensionProperties: true,
343
- addFoldersToTags: (_this$options$addFold = this.options.addFoldersToTags) != null ? _this$options$addFold : true
344
- },
345
- oasVersion: (_this$options$oasVers = this.options.oasVersion) != null ? _this$options$oasVers : '3.0',
346
- exportFormat: (_this$options$exportF = this.options.exportFormat) != null ? _this$options$exportF : 'JSON'
347
- }, {
348
- headers: {
349
- 'Authorization': "Bearer " + this.options.token,
350
- 'X-Apifox-Api-Version': '2024-03-28',
351
- 'Content-Type': 'application/json'
352
- }
353
- });
354
-
355
- case 3:
356
- response = _context.sent;
357
- this.openApiDoc = response.data;
358
-
359
- if ((_this$openApiDoc = this.openApiDoc) != null && (_this$openApiDoc$comp = _this$openApiDoc.components) != null && _this$openApiDoc$comp.schemas) {
360
- this.schemas = this.openApiDoc.components.schemas;
361
- }
362
-
363
- this.project._id = this.options.projectId;
364
- this.project.name = ((_this$openApiDoc2 = this.openApiDoc) == null ? void 0 : (_this$openApiDoc2$inf = _this$openApiDoc2.info) == null ? void 0 : _this$openApiDoc2$inf.title) || 'ApifoxProject';
365
- this.project.desc = ((_this$openApiDoc3 = this.openApiDoc) == null ? void 0 : (_this$openApiDoc3$inf = _this$openApiDoc3.info) == null ? void 0 : _this$openApiDoc3$inf.description) || '';
366
- return _context.abrupt("return", _extends({}, this.project, {
367
- cats: [],
368
- getMockUrl: function getMockUrl() {
369
- return '';
370
- },
371
- getDevUrl: function getDevUrl() {
372
- return '';
373
- },
374
- getProdUrl: function getProdUrl() {
375
- return '';
376
- }
377
- }));
378
-
379
- case 10:
380
- case "end":
381
- return _context.stop();
382
- }
383
- }
384
- }, _callee, this);
385
- }));
386
-
387
- function getProjectInfo() {
388
- return _getProjectInfo.apply(this, arguments);
389
- }
390
-
391
- return getProjectInfo;
392
- }();
393
-
394
- _proto.getTagId = function getTagId(tagName) {
395
- var hash = 0;
396
-
397
- for (var i = 0; i < tagName.length; i++) {
398
- var _char = tagName.charCodeAt(i);
399
-
400
- hash = (hash << 5) - hash + _char;
401
- hash = hash & hash;
402
- }
403
-
404
- return Math.abs(hash);
405
- }
406
- /**
407
- * 从 OpenAPI tags 中获取目录对应的 tag
408
- * 如果 x-apifox-folder 在 tags 中存在,则返回对应的 tag;否则返回 x-apifox-folder 本身
409
- */
410
- ;
411
-
412
- _proto.getMatchingTag = function getMatchingTag(xApifoxFolder) {
413
- var _this$openApiDoc4;
414
-
415
- if (!((_this$openApiDoc4 = this.openApiDoc) != null && _this$openApiDoc4.tags)) return null; // 先精确匹配
416
-
417
- var exactMatch = this.openApiDoc.tags.find(function (tag) {
418
- return tag.name === xApifoxFolder;
419
- });
420
- if (exactMatch) return exactMatch.name; // 如果 addFoldersToTags=true,可能目录名被添加到 tag 描述或其他地方
421
- // 或者 tag.name 本身就包含了目录路径
422
-
423
- return null;
424
- };
425
-
426
- _proto.getCats = function getCats(cats) {
427
- var _this2 = this;
428
-
429
- var categoryIds = getFilteredCat(cats, [], true);
430
-
431
- if (categoryIds.length === 0 || categoryIds.includes(0)) {
432
- var _this$openApiDoc5;
433
-
434
- if (!((_this$openApiDoc5 = this.openApiDoc) != null && _this$openApiDoc5.tags)) {
435
- return [];
436
- }
437
-
438
- var tagIds = this.openApiDoc.tags.map(function (tag) {
439
- return _this2.getTagId(tag.name);
440
- });
441
- return tagIds;
442
- }
443
-
444
- return categoryIds;
445
- };
446
-
447
- _proto.getInterfaceList = /*#__PURE__*/function () {
448
- var _getInterfaceList = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(syntheticalConfig) {
449
- var _this$openApiDoc6;
450
-
451
- var catId, interfaces, seenKeys, methods, _i3, _Object$entries3, _Object$entries3$_i, path, pathItem, _iterator, _step, method, operation, xApifoxFolder, tagId, key, categoryTag, interfaceInfo;
452
-
453
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
454
- while (1) {
455
- switch (_context2.prev = _context2.next) {
456
- case 0:
457
- if ((_this$openApiDoc6 = this.openApiDoc) != null && _this$openApiDoc6.paths) {
458
- _context2.next = 2;
459
- break;
460
- }
461
-
462
- return _context2.abrupt("return", []);
463
-
464
- case 2:
465
- catId = syntheticalConfig.id;
466
- interfaces = []; // 用于去重:基于 path + method
467
-
468
- seenKeys = new Set(); // 调试:获取 tag 名称
469
- // let tagName = 'unknown'
470
- // if (this.openApiDoc?.tags) {
471
- // const tag = this.openApiDoc.tags.find(
472
- // t => this.getTagId(t.name) === catId,
473
- // )
474
- // tagName = tag?.name || `unknown(${catId})`
475
- // }
476
-
477
- methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
478
- _i3 = 0, _Object$entries3 = Object.entries(this.openApiDoc.paths);
479
-
480
- case 7:
481
- if (!(_i3 < _Object$entries3.length)) {
482
- _context2.next = 30;
483
- break;
484
- }
485
-
486
- _Object$entries3$_i = _Object$entries3[_i3], path = _Object$entries3$_i[0], pathItem = _Object$entries3$_i[1];
487
- _iterator = _createForOfIteratorHelperLoose(methods);
488
-
489
- case 10:
490
- if ((_step = _iterator()).done) {
491
- _context2.next = 27;
492
- break;
493
- }
494
-
495
- method = _step.value;
496
- operation = pathItem[method];
497
-
498
- if (operation) {
499
- _context2.next = 15;
500
- break;
501
- }
502
-
503
- return _context2.abrupt("continue", 25);
504
-
505
- case 15:
506
- // 获取接口的目录信息:优先使用 x-apifox-folder
507
- xApifoxFolder = this.getOperationFolder(operation);
508
- tagId = this.getTagId(xApifoxFolder);
509
-
510
- if (!(tagId === catId)) {
511
- _context2.next = 25;
512
- break;
513
- }
514
-
515
- // 去重:基于 path + method
516
- key = method.toUpperCase() + ":" + path;
517
-
518
- if (!seenKeys.has(key)) {
519
- _context2.next = 21;
520
- break;
521
- }
522
-
523
- return _context2.abrupt("continue", 25);
524
-
525
- case 21:
526
- seenKeys.add(key); // 根据实际的目录名称来确定分类
527
-
528
- categoryTag = xApifoxFolder;
529
- interfaceInfo = this.convertOperationToInterface(path, method, operation, categoryTag, catId);
530
- interfaces.push(interfaceInfo);
531
-
532
- case 25:
533
- _context2.next = 10;
534
- break;
535
-
536
- case 27:
537
- _i3++;
538
- _context2.next = 7;
539
- break;
540
-
541
- case 30:
542
- return _context2.abrupt("return", interfaces);
543
-
544
- case 31:
545
- case "end":
546
- return _context2.stop();
547
- }
548
- }
549
- }, _callee2, this);
550
- }));
551
-
552
- function getInterfaceList(_x) {
553
- return _getInterfaceList.apply(this, arguments);
554
- }
555
-
556
- return getInterfaceList;
557
- }();
558
-
559
- return ApifoxToYApiData;
560
- }();
1
+ import { RequestBodyType, RequestFormItemType, RequestParamType, RequestQueryType, Required, ResponseBodyType } from "./types.js";
2
+ import { getFilteredCat } from "./utils.js";
3
+ import axios from "axios";
4
+ //#region src/ApifoxToYApiData.ts
5
+ var ApifoxToYApiData = class {
6
+ constructor(options) {
7
+ this.openApiDoc = null;
8
+ this.project = {
9
+ _id: 0,
10
+ _url: "",
11
+ name: "ApifoxProject",
12
+ desc: "",
13
+ basepath: "",
14
+ tag: [],
15
+ env: [{
16
+ name: "",
17
+ domain: ""
18
+ }]
19
+ };
20
+ this.schemas = {};
21
+ this.interfaceIdCounter = 0;
22
+ this.options = options;
23
+ }
24
+ resolveRef(ref) {
25
+ if (!ref.startsWith("#/components/schemas/")) return null;
26
+ const schemaName = ref.replace("#/components/schemas/", "");
27
+ return this.schemas[schemaName] || null;
28
+ }
29
+ resolveSchema(schema) {
30
+ if (!schema) return void 0;
31
+ if (schema.$ref) return this.resolveRef(schema.$ref) || schema;
32
+ return schema;
33
+ }
34
+ getSchemaType(schema) {
35
+ if (!schema) return "string";
36
+ const resolved = this.resolveSchema(schema);
37
+ if (!resolved) return "string";
38
+ return resolved.type || "string";
39
+ }
40
+ getRequestParamType(schema) {
41
+ const schemaType = this.getSchemaType(schema);
42
+ if (schemaType === "boolean") return RequestParamType.boolean;
43
+ if (schemaType === "number") return RequestParamType.number;
44
+ return RequestParamType.string;
45
+ }
46
+ getRequestQueryType(schema) {
47
+ const schemaType = this.getSchemaType(schema);
48
+ if (schemaType === "boolean") return RequestQueryType.boolean;
49
+ if (schemaType === "number") return RequestQueryType.number;
50
+ return RequestQueryType.string;
51
+ }
52
+ convertParametersToReqParams(parameters = [], type) {
53
+ if (type === "path") return parameters.filter((p) => p.in === "path").map((p) => ({
54
+ name: p.name,
55
+ desc: p.description || "",
56
+ example: p.example || "",
57
+ required: p.required ? Required.true : Required.false,
58
+ type: this.getRequestParamType(p.schema)
59
+ }));
60
+ return parameters.filter((p) => p.in === "query").map((p) => ({
61
+ name: p.name,
62
+ desc: p.description || "",
63
+ example: p.example || "",
64
+ required: p.required ? Required.true : Required.false,
65
+ type: this.getRequestQueryType(p.schema)
66
+ }));
67
+ }
68
+ convertRequestBody(requestBody) {
69
+ if (!requestBody?.content) return {
70
+ req_body_type: RequestBodyType.none,
71
+ req_body_is_json_schema: false,
72
+ req_body_form: [],
73
+ req_body_other: "",
74
+ contentType: ""
75
+ };
76
+ const contentTypes = Object.keys(requestBody.content);
77
+ const jsonContentType = contentTypes.find((ct) => ct.includes("application/json"));
78
+ const formContentType = contentTypes.find((ct) => ct.includes("application/x-www-form-urlencoded") || ct.includes("multipart/form-data"));
79
+ if (jsonContentType) {
80
+ const schema = requestBody.content[jsonContentType].schema;
81
+ return {
82
+ req_body_type: RequestBodyType.json,
83
+ req_body_is_json_schema: true,
84
+ req_body_form: [],
85
+ req_body_other: schema ? JSON.stringify(this.normalizeSchema(schema)) : "",
86
+ contentType: jsonContentType
87
+ };
88
+ }
89
+ if (formContentType) {
90
+ const schema = requestBody.content[formContentType].schema;
91
+ const resolvedSchema = this.resolveSchema(schema);
92
+ const formItems = [];
93
+ if (resolvedSchema?.properties) {
94
+ const requiredFields = resolvedSchema.required || [];
95
+ for (const [name, prop] of Object.entries(resolvedSchema.properties)) {
96
+ const resolvedProp = this.resolveSchema(prop);
97
+ formItems.push({
98
+ name,
99
+ type: resolvedProp?.type === "file" || resolvedProp?.format === "binary" ? RequestFormItemType.file : RequestFormItemType.text,
100
+ desc: resolvedProp?.description || "",
101
+ example: resolvedProp?.example || "",
102
+ required: requiredFields.includes(name) ? Required.true : Required.false
103
+ });
104
+ }
105
+ }
106
+ return {
107
+ req_body_type: RequestBodyType.form,
108
+ req_body_is_json_schema: false,
109
+ req_body_form: formItems,
110
+ req_body_other: "",
111
+ contentType: formContentType
112
+ };
113
+ }
114
+ return {
115
+ req_body_type: RequestBodyType.raw,
116
+ req_body_is_json_schema: false,
117
+ req_body_form: [],
118
+ req_body_other: "",
119
+ contentType: contentTypes[0] || ""
120
+ };
121
+ }
122
+ normalizeSchema(schema, visited = /* @__PURE__ */ new Set()) {
123
+ const result = { ...schema };
124
+ if (schema.$ref) {
125
+ if (visited.has(schema.$ref)) return { type: "object" };
126
+ visited.add(schema.$ref);
127
+ const resolved = this.resolveRef(schema.$ref);
128
+ if (resolved) return this.normalizeSchema(resolved, new Set(visited));
129
+ }
130
+ if (schema.properties) {
131
+ result.properties = {};
132
+ for (const [key, value] of Object.entries(schema.properties)) if (value) result.properties[key] = this.normalizeSchema(value, new Set(visited));
133
+ }
134
+ if (schema.items) result.items = this.normalizeSchema(schema.items, new Set(visited));
135
+ delete result.$ref;
136
+ return result;
137
+ }
138
+ convertResponse(responses = {}) {
139
+ const successResponse = responses["200"] || responses["201"] || responses["204"];
140
+ if (!successResponse?.content) return {
141
+ res_body_type: ResponseBodyType.json,
142
+ res_body_is_json_schema: false,
143
+ res_body: ""
144
+ };
145
+ const jsonContent = successResponse.content["application/json"];
146
+ if (jsonContent?.schema) return {
147
+ res_body_type: ResponseBodyType.json,
148
+ res_body_is_json_schema: true,
149
+ res_body: JSON.stringify(this.normalizeSchema(jsonContent.schema))
150
+ };
151
+ return {
152
+ res_body_type: ResponseBodyType.json,
153
+ res_body_is_json_schema: false,
154
+ res_body: ""
155
+ };
156
+ }
157
+ generateInterfaceId() {
158
+ this.interfaceIdCounter += 1;
159
+ return this.interfaceIdCounter;
160
+ }
161
+ /**
162
+ * 获取接口所属的目录名称
163
+ * Apifox 优先使用 x-apifox-folder 字段,其次才用 tags[0]
164
+ */
165
+ getOperationFolder(operation) {
166
+ if (operation["x-apifox-folder"]) return operation["x-apifox-folder"];
167
+ if (operation.tags && operation.tags.length > 0) return operation.tags[0];
168
+ return "default";
169
+ }
170
+ convertOperationToInterface(path, method, operation, categoryName, categoryId) {
171
+ const parameters = operation.parameters || [];
172
+ const pathParams = this.convertParametersToReqParams(parameters, "path");
173
+ const queryParams = this.convertParametersToReqParams(parameters, "query");
174
+ const reqHeaders = [];
175
+ const requestBodyInfo = this.convertRequestBody(operation.requestBody);
176
+ if (requestBodyInfo.contentType) reqHeaders.push({
177
+ name: "Content-Type",
178
+ value: requestBodyInfo.contentType,
179
+ desc: "",
180
+ example: "",
181
+ required: Required.true
182
+ });
183
+ const responseInfo = this.convertResponse(operation.responses);
184
+ const folderName = this.getOperationFolder(operation);
185
+ const interfaceTags = operation.tags?.length ? operation.tags : folderName !== "default" ? [folderName] : [];
186
+ return {
187
+ _id: this.generateInterfaceId(),
188
+ _category: {
189
+ _id: categoryId,
190
+ _url: "",
191
+ name: categoryName,
192
+ desc: "",
193
+ add_time: 0,
194
+ up_time: 0
195
+ },
196
+ _project: { ...this.project },
197
+ _url: "",
198
+ title: operation.summary || path,
199
+ status: operation.deprecated ? "undone" : "done",
200
+ markdown: operation.description || "",
201
+ path,
202
+ method: method.toUpperCase(),
203
+ project_id: this.options.projectId,
204
+ catid: categoryId,
205
+ tag: interfaceTags,
206
+ req_headers: reqHeaders,
207
+ req_params: pathParams,
208
+ req_query: queryParams,
209
+ req_body_type: requestBodyInfo.req_body_type,
210
+ req_body_is_json_schema: requestBodyInfo.req_body_is_json_schema,
211
+ req_body_form: requestBodyInfo.req_body_form,
212
+ req_body_other: requestBodyInfo.req_body_other,
213
+ res_body_type: responseInfo.res_body_type,
214
+ res_body_is_json_schema: responseInfo.res_body_is_json_schema,
215
+ res_body: responseInfo.res_body,
216
+ add_time: Math.floor(Date.now() / 1e3),
217
+ up_time: Math.floor(Date.now() / 1e3),
218
+ uid: 0
219
+ };
220
+ }
221
+ async getProjectInfo() {
222
+ const baseUrl = this.options.serverUrl.replace(/\/$/, "");
223
+ const response = await axios.post(`${baseUrl}/v1/projects/${this.options.projectId}/export-openapi?locale=zh-CN`, {
224
+ scope: {
225
+ type: "ALL",
226
+ excludedByTags: []
227
+ },
228
+ options: {
229
+ includeApifoxExtensionProperties: true,
230
+ addFoldersToTags: this.options.addFoldersToTags ?? true
231
+ },
232
+ oasVersion: this.options.oasVersion ?? "3.0",
233
+ exportFormat: this.options.exportFormat ?? "JSON"
234
+ }, { headers: {
235
+ Authorization: `Bearer ${this.options.token}`,
236
+ "X-Apifox-Api-Version": "2024-03-28",
237
+ "Content-Type": "application/json"
238
+ } });
239
+ this.openApiDoc = response.data;
240
+ if (this.openApiDoc?.components?.schemas) this.schemas = this.openApiDoc.components.schemas;
241
+ this.project._id = this.options.projectId;
242
+ this.project.name = this.openApiDoc?.info?.title || "ApifoxProject";
243
+ this.project.desc = this.openApiDoc?.info?.description || "";
244
+ return {
245
+ ...this.project,
246
+ cats: [],
247
+ getMockUrl: () => "",
248
+ getDevUrl: () => "",
249
+ getProdUrl: () => ""
250
+ };
251
+ }
252
+ getTagId(tagName) {
253
+ let hash = 0;
254
+ for (let i = 0; i < tagName.length; i++) {
255
+ const char = tagName.charCodeAt(i);
256
+ hash = (hash << 5) - hash + char;
257
+ hash = hash & hash;
258
+ }
259
+ return Math.abs(hash);
260
+ }
261
+ /**
262
+ * 从 OpenAPI tags 中获取目录对应的 tag
263
+ * 如果 x-apifox-folder 在 tags 中存在,则返回对应的 tag;否则返回 x-apifox-folder 本身
264
+ */
265
+ getMatchingTag(xApifoxFolder) {
266
+ if (!this.openApiDoc?.tags) return null;
267
+ const exactMatch = this.openApiDoc.tags.find((tag) => tag.name === xApifoxFolder);
268
+ if (exactMatch) return exactMatch.name;
269
+ return null;
270
+ }
271
+ getCats(cats) {
272
+ const categoryIds = getFilteredCat(cats, [], true);
273
+ if (categoryIds.length === 0 || categoryIds.includes(0)) {
274
+ if (!this.openApiDoc?.tags) return [];
275
+ return this.openApiDoc.tags.map((tag) => this.getTagId(tag.name));
276
+ }
277
+ return categoryIds;
278
+ }
279
+ async getInterfaceList(syntheticalConfig) {
280
+ if (!this.openApiDoc?.paths) return [];
281
+ const catId = syntheticalConfig.id;
282
+ const interfaces = [];
283
+ const seenKeys = /* @__PURE__ */ new Set();
284
+ const methods = [
285
+ "get",
286
+ "post",
287
+ "put",
288
+ "delete",
289
+ "patch",
290
+ "head",
291
+ "options"
292
+ ];
293
+ for (const [path, pathItem] of Object.entries(this.openApiDoc.paths)) for (const method of methods) {
294
+ const operation = pathItem[method];
295
+ if (!operation) continue;
296
+ const xApifoxFolder = this.getOperationFolder(operation);
297
+ if (this.getTagId(xApifoxFolder) === catId) {
298
+ const key = `${method.toUpperCase()}:${path}`;
299
+ if (seenKeys.has(key)) continue;
300
+ seenKeys.add(key);
301
+ const categoryTag = xApifoxFolder;
302
+ const interfaceInfo = this.convertOperationToInterface(path, method, operation, categoryTag, catId);
303
+ interfaces.push(interfaceInfo);
304
+ }
305
+ }
306
+ return interfaces;
307
+ }
308
+ };
309
+ //#endregion
310
+ export { ApifoxToYApiData };