yapi-apifox-to-typescript 4.2.3 → 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,572 +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, visited) {
177
- if (visited === void 0) {
178
- visited = new Set();
179
- }
180
-
181
- var result = _extends({}, schema);
182
-
183
- if (schema.$ref) {
184
- if (visited.has(schema.$ref)) {
185
- // 循环引用,直接返回空对象避免无限递归
186
- return {
187
- type: 'object'
188
- };
189
- }
190
-
191
- visited.add(schema.$ref);
192
- var resolved = this.resolveRef(schema.$ref);
193
-
194
- if (resolved) {
195
- return this.normalizeSchema(resolved, new Set(visited));
196
- }
197
- }
198
-
199
- if (schema.properties) {
200
- result.properties = {};
201
-
202
- for (var _i2 = 0, _Object$entries2 = Object.entries(schema.properties); _i2 < _Object$entries2.length; _i2++) {
203
- var _Object$entries2$_i = _Object$entries2[_i2],
204
- key = _Object$entries2$_i[0],
205
- _value = _Object$entries2$_i[1];
206
-
207
- if (_value) {
208
- result.properties[key] = this.normalizeSchema(_value, new Set(visited));
209
- }
210
- }
211
- }
212
-
213
- if (schema.items) {
214
- result.items = this.normalizeSchema(schema.items, new Set(visited));
215
- }
216
-
217
- delete result.$ref;
218
- return result;
219
- };
220
-
221
- _proto.convertResponse = function convertResponse(responses) {
222
- if (responses === void 0) {
223
- responses = {};
224
- }
225
-
226
- var successResponse = responses['200'] || responses['201'] || responses['204'];
227
-
228
- if (!(successResponse != null && successResponse.content)) {
229
- return {
230
- res_body_type: ResponseBodyType.json,
231
- res_body_is_json_schema: false,
232
- res_body: ''
233
- };
234
- }
235
-
236
- var jsonContent = successResponse.content['application/json'];
237
-
238
- if (jsonContent != null && jsonContent.schema) {
239
- return {
240
- res_body_type: ResponseBodyType.json,
241
- res_body_is_json_schema: true,
242
- res_body: JSON.stringify(this.normalizeSchema(jsonContent.schema))
243
- };
244
- }
245
-
246
- return {
247
- res_body_type: ResponseBodyType.json,
248
- res_body_is_json_schema: false,
249
- res_body: ''
250
- };
251
- };
252
-
253
- _proto.generateInterfaceId = function generateInterfaceId() {
254
- this.interfaceIdCounter += 1;
255
- return this.interfaceIdCounter;
256
- }
257
- /**
258
- * 获取接口所属的目录名称
259
- * Apifox 优先使用 x-apifox-folder 字段,其次才用 tags[0]
260
- */
261
- ;
262
-
263
- _proto.getOperationFolder = function getOperationFolder(operation) {
264
- // 优先使用 x-apifox-folder
265
- if (operation['x-apifox-folder']) {
266
- return operation['x-apifox-folder'];
267
- } // 其次使用 tags[0]
268
-
269
-
270
- if (operation.tags && operation.tags.length > 0) {
271
- return operation.tags[0];
272
- }
273
-
274
- return 'default';
275
- };
276
-
277
- _proto.convertOperationToInterface = function convertOperationToInterface(path, method, operation, categoryName, categoryId) {
278
- var _operation$tags;
279
-
280
- var parameters = operation.parameters || [];
281
- var pathParams = this.convertParametersToReqParams(parameters, 'path');
282
- var queryParams = this.convertParametersToReqParams(parameters, 'query'); // const headerParams = parameters.filter(p => p.in === 'header')
283
-
284
- var reqHeaders = [];
285
- var requestBodyInfo = this.convertRequestBody(operation.requestBody);
286
-
287
- if (requestBodyInfo.contentType) {
288
- reqHeaders.push({
289
- name: 'Content-Type',
290
- value: requestBodyInfo.contentType,
291
- desc: '',
292
- example: '',
293
- required: Required.true
294
- });
295
- }
296
-
297
- var responseInfo = this.convertResponse(operation.responses); // 获取接口所属目录:优先使用 x-apifox-folder,其次用 tags
298
-
299
- var folderName = this.getOperationFolder(operation);
300
- var interfaceTags = (_operation$tags = operation.tags) != null && _operation$tags.length ? operation.tags : folderName !== 'default' ? [folderName] : [];
301
- return {
302
- _id: this.generateInterfaceId(),
303
- _category: {
304
- _id: categoryId,
305
- _url: '',
306
- name: categoryName,
307
- desc: '',
308
- add_time: 0,
309
- up_time: 0
310
- },
311
- _project: _extends({}, this.project),
312
- _url: '',
313
- title: operation.summary || path,
314
- status: operation.deprecated ? 'undone' : 'done',
315
- markdown: operation.description || '',
316
- path: path,
317
- method: method.toUpperCase(),
318
- project_id: this.options.projectId,
319
- catid: categoryId,
320
- tag: interfaceTags,
321
- req_headers: reqHeaders,
322
- req_params: pathParams,
323
- req_query: queryParams,
324
- req_body_type: requestBodyInfo.req_body_type,
325
- req_body_is_json_schema: requestBodyInfo.req_body_is_json_schema,
326
- req_body_form: requestBodyInfo.req_body_form,
327
- req_body_other: requestBodyInfo.req_body_other,
328
- res_body_type: responseInfo.res_body_type,
329
- res_body_is_json_schema: responseInfo.res_body_is_json_schema,
330
- res_body: responseInfo.res_body,
331
- add_time: Math.floor(Date.now() / 1000),
332
- up_time: Math.floor(Date.now() / 1000),
333
- uid: 0
334
- };
335
- };
336
-
337
- _proto.getProjectInfo = /*#__PURE__*/function () {
338
- var _getProjectInfo = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
339
- 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;
340
-
341
- var baseUrl, response;
342
- return _regeneratorRuntime.wrap(function _callee$(_context) {
343
- while (1) {
344
- switch (_context.prev = _context.next) {
345
- case 0:
346
- baseUrl = this.options.serverUrl.replace(/\/$/, '');
347
- _context.next = 3;
348
- return axios.post(baseUrl + "/v1/projects/" + this.options.projectId + "/export-openapi?locale=zh-CN", {
349
- scope: {
350
- type: 'ALL',
351
- excludedByTags: []
352
- },
353
- options: {
354
- includeApifoxExtensionProperties: true,
355
- addFoldersToTags: (_this$options$addFold = this.options.addFoldersToTags) != null ? _this$options$addFold : true
356
- },
357
- oasVersion: (_this$options$oasVers = this.options.oasVersion) != null ? _this$options$oasVers : '3.0',
358
- exportFormat: (_this$options$exportF = this.options.exportFormat) != null ? _this$options$exportF : 'JSON'
359
- }, {
360
- headers: {
361
- 'Authorization': "Bearer " + this.options.token,
362
- 'X-Apifox-Api-Version': '2024-03-28',
363
- 'Content-Type': 'application/json'
364
- }
365
- });
366
-
367
- case 3:
368
- response = _context.sent;
369
- this.openApiDoc = response.data;
370
-
371
- if ((_this$openApiDoc = this.openApiDoc) != null && (_this$openApiDoc$comp = _this$openApiDoc.components) != null && _this$openApiDoc$comp.schemas) {
372
- this.schemas = this.openApiDoc.components.schemas;
373
- }
374
-
375
- this.project._id = this.options.projectId;
376
- this.project.name = ((_this$openApiDoc2 = this.openApiDoc) == null ? void 0 : (_this$openApiDoc2$inf = _this$openApiDoc2.info) == null ? void 0 : _this$openApiDoc2$inf.title) || 'ApifoxProject';
377
- this.project.desc = ((_this$openApiDoc3 = this.openApiDoc) == null ? void 0 : (_this$openApiDoc3$inf = _this$openApiDoc3.info) == null ? void 0 : _this$openApiDoc3$inf.description) || '';
378
- return _context.abrupt("return", _extends({}, this.project, {
379
- cats: [],
380
- getMockUrl: function getMockUrl() {
381
- return '';
382
- },
383
- getDevUrl: function getDevUrl() {
384
- return '';
385
- },
386
- getProdUrl: function getProdUrl() {
387
- return '';
388
- }
389
- }));
390
-
391
- case 10:
392
- case "end":
393
- return _context.stop();
394
- }
395
- }
396
- }, _callee, this);
397
- }));
398
-
399
- function getProjectInfo() {
400
- return _getProjectInfo.apply(this, arguments);
401
- }
402
-
403
- return getProjectInfo;
404
- }();
405
-
406
- _proto.getTagId = function getTagId(tagName) {
407
- var hash = 0;
408
-
409
- for (var i = 0; i < tagName.length; i++) {
410
- var _char = tagName.charCodeAt(i);
411
-
412
- hash = (hash << 5) - hash + _char;
413
- hash = hash & hash;
414
- }
415
-
416
- return Math.abs(hash);
417
- }
418
- /**
419
- * 从 OpenAPI tags 中获取目录对应的 tag
420
- * 如果 x-apifox-folder 在 tags 中存在,则返回对应的 tag;否则返回 x-apifox-folder 本身
421
- */
422
- ;
423
-
424
- _proto.getMatchingTag = function getMatchingTag(xApifoxFolder) {
425
- var _this$openApiDoc4;
426
-
427
- if (!((_this$openApiDoc4 = this.openApiDoc) != null && _this$openApiDoc4.tags)) return null; // 先精确匹配
428
-
429
- var exactMatch = this.openApiDoc.tags.find(function (tag) {
430
- return tag.name === xApifoxFolder;
431
- });
432
- if (exactMatch) return exactMatch.name; // 如果 addFoldersToTags=true,可能目录名被添加到 tag 描述或其他地方
433
- // 或者 tag.name 本身就包含了目录路径
434
-
435
- return null;
436
- };
437
-
438
- _proto.getCats = function getCats(cats) {
439
- var _this2 = this;
440
-
441
- var categoryIds = getFilteredCat(cats, [], true);
442
-
443
- if (categoryIds.length === 0 || categoryIds.includes(0)) {
444
- var _this$openApiDoc5;
445
-
446
- if (!((_this$openApiDoc5 = this.openApiDoc) != null && _this$openApiDoc5.tags)) {
447
- return [];
448
- }
449
-
450
- var tagIds = this.openApiDoc.tags.map(function (tag) {
451
- return _this2.getTagId(tag.name);
452
- });
453
- return tagIds;
454
- }
455
-
456
- return categoryIds;
457
- };
458
-
459
- _proto.getInterfaceList = /*#__PURE__*/function () {
460
- var _getInterfaceList = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(syntheticalConfig) {
461
- var _this$openApiDoc6;
462
-
463
- var catId, interfaces, seenKeys, methods, _i3, _Object$entries3, _Object$entries3$_i, path, pathItem, _iterator, _step, method, operation, xApifoxFolder, tagId, key, categoryTag, interfaceInfo;
464
-
465
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
466
- while (1) {
467
- switch (_context2.prev = _context2.next) {
468
- case 0:
469
- if ((_this$openApiDoc6 = this.openApiDoc) != null && _this$openApiDoc6.paths) {
470
- _context2.next = 2;
471
- break;
472
- }
473
-
474
- return _context2.abrupt("return", []);
475
-
476
- case 2:
477
- catId = syntheticalConfig.id;
478
- interfaces = []; // 用于去重:基于 path + method
479
-
480
- seenKeys = new Set(); // 调试:获取 tag 名称
481
- // let tagName = 'unknown'
482
- // if (this.openApiDoc?.tags) {
483
- // const tag = this.openApiDoc.tags.find(
484
- // t => this.getTagId(t.name) === catId,
485
- // )
486
- // tagName = tag?.name || `unknown(${catId})`
487
- // }
488
-
489
- methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
490
- _i3 = 0, _Object$entries3 = Object.entries(this.openApiDoc.paths);
491
-
492
- case 7:
493
- if (!(_i3 < _Object$entries3.length)) {
494
- _context2.next = 30;
495
- break;
496
- }
497
-
498
- _Object$entries3$_i = _Object$entries3[_i3], path = _Object$entries3$_i[0], pathItem = _Object$entries3$_i[1];
499
- _iterator = _createForOfIteratorHelperLoose(methods);
500
-
501
- case 10:
502
- if ((_step = _iterator()).done) {
503
- _context2.next = 27;
504
- break;
505
- }
506
-
507
- method = _step.value;
508
- operation = pathItem[method];
509
-
510
- if (operation) {
511
- _context2.next = 15;
512
- break;
513
- }
514
-
515
- return _context2.abrupt("continue", 25);
516
-
517
- case 15:
518
- // 获取接口的目录信息:优先使用 x-apifox-folder
519
- xApifoxFolder = this.getOperationFolder(operation);
520
- tagId = this.getTagId(xApifoxFolder);
521
-
522
- if (!(tagId === catId)) {
523
- _context2.next = 25;
524
- break;
525
- }
526
-
527
- // 去重:基于 path + method
528
- key = method.toUpperCase() + ":" + path;
529
-
530
- if (!seenKeys.has(key)) {
531
- _context2.next = 21;
532
- break;
533
- }
534
-
535
- return _context2.abrupt("continue", 25);
536
-
537
- case 21:
538
- seenKeys.add(key); // 根据实际的目录名称来确定分类
539
-
540
- categoryTag = xApifoxFolder;
541
- interfaceInfo = this.convertOperationToInterface(path, method, operation, categoryTag, catId);
542
- interfaces.push(interfaceInfo);
543
-
544
- case 25:
545
- _context2.next = 10;
546
- break;
547
-
548
- case 27:
549
- _i3++;
550
- _context2.next = 7;
551
- break;
552
-
553
- case 30:
554
- return _context2.abrupt("return", interfaces);
555
-
556
- case 31:
557
- case "end":
558
- return _context2.stop();
559
- }
560
- }
561
- }, _callee2, this);
562
- }));
563
-
564
- function getInterfaceList(_x) {
565
- return _getInterfaceList.apply(this, arguments);
566
- }
567
-
568
- return getInterfaceList;
569
- }();
570
-
571
- return ApifoxToYApiData;
572
- }();
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 };