Skip to content

Commit adabc4d

Browse files
committed
Improve schema parsing and error handling in API debug tools
Enhances the TypeBox schema parser to better handle deep nesting, circular references, and union truncation, and adds error handling for schema parsing and default value generation in the OneBot API debug UI. Updates the display component to show clear messages for circular or truncated schemas, and improves robustness in HTTP debug command execution. Also synchronizes the ParsedSchema type in the Zod utility for consistency.
1 parent bf073b5 commit adabc4d

5 files changed

Lines changed: 152 additions & 103 deletions

File tree

packages/napcat-webui-frontend/src/components/onebot/api/debug.tsx

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Tab, Tabs } from '@heroui/tabs';
77
import { Chip } from '@heroui/chip';
88
import { useLocalStorage } from '@uidotdev/usehooks';
99
import clsx from 'clsx';
10-
import { forwardRef, useEffect, useImperativeHandle, useState, useCallback } from 'react';
10+
import { forwardRef, useEffect, useImperativeHandle, useState, useCallback, useMemo } from 'react';
1111
import toast from 'react-hot-toast';
1212
import { IoChevronDown, IoSend, IoSettingsSharp, IoCopy } from 'react-icons/io5';
1313
import { TbCode, TbMessageCode } from 'react-icons/tb';
@@ -59,16 +59,30 @@ const OneBotApiDebug = forwardRef<OneBotApiDebugRef, OneBotApiDebugProps>((props
5959
const [responseHeight, setResponseHeight] = useState(240);
6060
const [storedHeight, setStoredHeight] = useLocalStorage('napcat_debug_response_height', 240);
6161

62-
const parsedRequest = parseTypeBox(data?.payload);
62+
// 使用 useMemo 缓存解析结果,避免每次渲染都重新解析
63+
const parsedRequest = useMemo(() => {
64+
try {
65+
return parseTypeBox(data?.payload);
66+
} catch (e) {
67+
console.error('Error parsing request schema:', e);
68+
return [];
69+
}
70+
}, [data?.payload]);
6371

6472
// 将返回值的 data 结构包装进 BaseResponseSchema 进行展示
6573
// 使用解构属性的方式重新构建对象,确保 parseTypeBox 能够识别为 object 类型
66-
const wrappedResponseSchema = Type.Object({
67-
...BaseResponseSchema.properties,
68-
data: data?.response || Type.Any({ description: '数据' })
69-
});
70-
71-
const parsedResponse = parseTypeBox(wrappedResponseSchema);
74+
const parsedResponse = useMemo(() => {
75+
try {
76+
const wrappedResponseSchema = Type.Object({
77+
...BaseResponseSchema.properties,
78+
data: data?.response || Type.Any({ description: '数据' })
79+
});
80+
return parseTypeBox(wrappedResponseSchema);
81+
} catch (e) {
82+
console.error('Error parsing response schema:', e);
83+
return [];
84+
}
85+
}, [data?.response]);
7286
const [backgroundImage] = useLocalStorage<string>(key.backgroundImage, '');
7387
const hasBackground = !!backgroundImage;
7488

@@ -166,7 +180,12 @@ const OneBotApiDebug = forwardRef<OneBotApiDebugRef, OneBotApiDebugProps>((props
166180
if (data?.payloadExample) {
167181
setRequestBody(JSON.stringify(data.payloadExample, null, 2));
168182
} else {
169-
setRequestBody(JSON.stringify(generateDefaultFromTypeBox(data?.payload), null, 2));
183+
try {
184+
setRequestBody(JSON.stringify(generateDefaultFromTypeBox(data?.payload), null, 2));
185+
} catch (e) {
186+
console.error('Error generating default:', e);
187+
setRequestBody('{}');
188+
}
170189
}
171190
setResponseContent('');
172191
setResponseStatus(null);
@@ -320,7 +339,14 @@ const OneBotApiDebug = forwardRef<OneBotApiDebugRef, OneBotApiDebugProps>((props
320339
)}
321340
</ChatInputModal>
322341
<Tooltip content="生成示例" closeDelay={0}>
323-
<Button isIconOnly size='sm' variant='light' radius='sm' className='w-8 h-8' onPress={() => setRequestBody(JSON.stringify(generateDefaultFromTypeBox(data?.payload), null, 2))}>
342+
<Button isIconOnly size='sm' variant='light' radius='sm' className='w-8 h-8' onPress={() => {
343+
try {
344+
setRequestBody(JSON.stringify(generateDefaultFromTypeBox(data?.payload), null, 2));
345+
} catch (e) {
346+
console.error('Error generating default:', e);
347+
toast.error('生成示例失败');
348+
}
349+
}}>
324350
<TbCode size={16} />
325351
</Button>
326352
</Tooltip>

packages/napcat-webui-frontend/src/components/onebot/api/display_struct.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ const SchemaContainer: React.FC<{
127127
};
128128

129129
const RenderSchema: React.FC<{ schema: ParsedSchema; }> = ({ schema }) => {
130+
// 处理循环引用和截断的情况,直接显示提示而不继续递归
131+
if (schema.isCircularRef || schema.isTruncated) {
132+
return (
133+
<div className='mb-2 flex items-center gap-1 pl-5'>
134+
{schema.name && (
135+
<span className='text-default-400'>{schema.name}</span>
136+
)}
137+
<Chip size='sm' color='default' variant='flat'>
138+
{schema.description || '...'}
139+
</Chip>
140+
</div>
141+
);
142+
}
143+
130144
if (schema.type === 'object') {
131145
return (
132146
<SchemaContainer schema={schema}>

packages/napcat-webui-frontend/src/pages/dashboard/debug/http/index.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,17 @@ export default function HttpDebug () {
112112
const executeCommand = (commandId: string, mode: CommandPaletteExecuteMode) => {
113113
const api = commandId as OneBotHttpApiPath;
114114
const item = oneBotHttpApi[api];
115-
const body = item?.payloadExample
116-
? JSON.stringify(item.payloadExample, null, 2)
117-
: (item?.payload ? JSON.stringify(generateDefaultFromTypeBox(item.payload), null, 2) : '{}');
115+
let body = '{}';
116+
if (item?.payloadExample) {
117+
body = JSON.stringify(item.payloadExample, null, 2);
118+
} else if (item?.payload) {
119+
try {
120+
body = JSON.stringify(generateDefaultFromTypeBox(item.payload), null, 2);
121+
} catch (e) {
122+
console.error('Error generating default:', e);
123+
body = '{}';
124+
}
125+
}
118126

119127
handleSelectApi(api);
120128
// 确保请求参数可见

0 commit comments

Comments
 (0)