Skip to content

Commit

Permalink
feat: 支持定义引用类型以满足需要复用类型的场景
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Feb 26, 2022
1 parent ab80fd1 commit d022db1
Show file tree
Hide file tree
Showing 9 changed files with 1,707 additions and 24 deletions.
Binary file added docs/images/refAbsolute.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/refArrayItem.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/refRelative.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/refTree.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,33 @@ export default defineConfig(
},
)
```

## 定义引用类型 <Badge>3.32.0+</Badge>

该功能适用于定义树等需要复用类型的场景。

YApi 本身是不支持定义引用类型的,本功能仅是在 YApi 自有功能上做了以下约定支持定义引用类型:

- 必须在 **标题** 栏定义;
- 必须以 **&** 开头;
- 引用路径规范和文件路径规范一致:`.` 表示当前级,`..` 表示上一级,`/` 在首位时表示根级,在中间时表示分割;
- 引用数组的条目时必须加上 `/0`
- 定义引用类型后原本的类型定义将失效。

举例:

- 定义树

<img src="./images/refTree.png" width="800" />

- 使用绝对路径引用

<img src="./images/refAbsolute.png" width="800" />

- 使用相对路径引用

<img src="./images/refRelative.png" width="800" />

- 引用数组条目

<img src="./images/refArrayItem.png" width="800" />
52 changes: 38 additions & 14 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isObject,
mapKeys,
memoize,
repeat,
run,
traverse,
} from 'vtils'
Expand Down Expand Up @@ -67,7 +68,11 @@ export function getNormalizedRelativePath(from: string, to: string) {
*/
export function traverseJsonSchema(
jsonSchema: JSONSchema4,
cb: (jsonSchema: JSONSchema4) => JSONSchema4,
cb: (
jsonSchema: JSONSchema4,
currentPath: Array<string | number>,
) => JSONSchema4,
currentPath: Array<string | number> = [],
): JSONSchema4 {
/* istanbul ignore if */
if (!isObject(jsonSchema)) return jsonSchema
Expand All @@ -83,31 +88,35 @@ export function traverseJsonSchema(
}

// 处理传入的 JSONSchema
cb(jsonSchema)
cb(jsonSchema, currentPath)

// 继续处理对象的子元素
if (jsonSchema.properties) {
forOwn(jsonSchema.properties, item => traverseJsonSchema(item, cb))
forOwn(jsonSchema.properties, (item, key) =>
traverseJsonSchema(item, cb, [...currentPath, key]),
)
}

// 继续处理数组的子元素
if (jsonSchema.items) {
castArray(jsonSchema.items).forEach(item => traverseJsonSchema(item, cb))
castArray(jsonSchema.items).forEach((item, index) =>
traverseJsonSchema(item, cb, [...currentPath, index]),
)
}

// 处理 oneOf
if (jsonSchema.oneOf) {
jsonSchema.oneOf.forEach(item => traverseJsonSchema(item, cb))
jsonSchema.oneOf.forEach(item => traverseJsonSchema(item, cb, currentPath))
}

// 处理 anyOf
if (jsonSchema.anyOf) {
jsonSchema.anyOf.forEach(item => traverseJsonSchema(item, cb))
jsonSchema.anyOf.forEach(item => traverseJsonSchema(item, cb, currentPath))
}

// 处理 allOf
if (jsonSchema.allOf) {
jsonSchema.allOf.forEach(item => traverseJsonSchema(item, cb))
jsonSchema.allOf.forEach(item => traverseJsonSchema(item, cb, currentPath))
}

return jsonSchema
Expand All @@ -128,11 +137,6 @@ export function processJsonSchema(
delete jsonSchema.$ref
delete jsonSchema.$$ref

// 删除 Mockjs.toJSONSchema 引入的键
delete jsonSchema.template
delete jsonSchema.rule
delete jsonSchema.path

// 数组只取第一个判断类型
if (
jsonSchema.type === 'array' &&
Expand Down Expand Up @@ -192,12 +196,32 @@ export function processJsonSchema(
*/
export function jsonSchemaToJSTTJsonSchema(
jsonSchema: JSONSchema4,
typeName: string,
): JSONSchema4 {
if (jsonSchema) {
// 去除最外层的 description 以防止 JSTT 提取它作为类型的注释
delete jsonSchema.description
}
return traverseJsonSchema(jsonSchema, jsonSchema => {
return traverseJsonSchema(jsonSchema, (jsonSchema, currentPath) => {
// 支持类型引用
if (jsonSchema.title?.startsWith('&')) {
const typeRelativePath = jsonSchema.title.substring(1)
const typeAbsolutePath = toUnixPath(
path.resolve(
path.dirname(`/${currentPath.join('/')}`.replace(/\/{2,}/g, '/')),
typeRelativePath,
),
)
const typeAbsolutePathArr = typeAbsolutePath.split('/').filter(Boolean)
const tsType = `${repeat(
'NonNullable<',
typeAbsolutePathArr.length,
)}${typeName}[${typeAbsolutePathArr
.map(v => JSON.stringify(v))
.join(']>[')}]>`
jsonSchema.tsType = tsType
}

// 去除 title 和 id,防止 json-schema-to-typescript 提取它们作为接口名
delete jsonSchema.title
delete jsonSchema.id
Expand Down Expand Up @@ -380,7 +404,7 @@ export async function jsonSchemaToType(
// JSTT 会转换 typeName,因此传入一个全大写的假 typeName,生成代码后再替换回真正的 typeName
const fakeTypeName = 'THISISAFAKETYPENAME'
const code = await compile(
jsonSchemaToJSTTJsonSchema(cloneDeepFast(jsonSchema)),
jsonSchemaToJSTTJsonSchema(cloneDeepFast(jsonSchema), typeName),
fakeTypeName,
JSTTOptions,
)
Expand Down
33 changes: 33 additions & 0 deletions tests/__mocks__/got.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,39 @@ const mockData: Record<string, () => any> = {
res_body:
'{\n "pageNo": 1,\n "pageSize": 5,\n "count": 16,\n "first": 1,\n "last": 1,\n "prev": 1,\n "next": 1,\n "firstPage": true,\n "lastPage": true,\n "length": 8,\n "slider": 1,\n "orderBy": "",\n "funcName": "page",\n "funcParam": "",\n "message": "",\n "html": "",\n "totalPage": 1,\n "notCount": false,\n "maxResults": 5,\n "disabled": false,\n "firstResult": 0\n}',
},
{
query_path: {
path: '/recursion',
params: [],
},
edit_uid: 0,
status: 'undone',
type: 'static',
req_body_is_json_schema: false,
res_body_is_json_schema: true,
api_opened: false,
index: 0,
tag: [],
_id: 1832,
method: 'GET',
catid: 20,
title: '递归',
path: '/recursion',
project_id: 11,
req_params: [],
res_body_type: 'json',
uid: 11,
add_time: 1645844560,
up_time: 1645849095,
req_query: [],
req_headers: [],
req_body_form: [],
__v: 0,
desc: '',
markdown: '',
res_body:
'{"type":"object","title":"title","properties":{"list":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"},"children":{"type":"array","items":{"type":"object","properties":{}},"title":"&.."},"id":{"type":"number"},"parent":{"type":"string","title":"&."}},"required":["label","value","id"]}},"list2":{"type":"array","items":{"type":"object","properties":{}},"title":"&./list"},"obj":{"type":"array","items":{"type":"object","properties":{"list3":{"type":"array","items":{"type":"object","properties":{}},"title":"&/list"},"name":{"type":"number"},"name2":{"type":"string","title":"&./name"},"id2":{"type":"string","title":"&/list/0/id"}},"required":["list3","name","name2","id2"]}}},"required":["list","list2","obj"]}',
},
{
query_path: {
path: '/default_value',
Expand Down
Loading

0 comments on commit d022db1

Please sign in to comment.