refactor(schema): 提取并复用 OpenAPI 响应示例,减少重复定义

在 napcat-schema 中引入 ActionExamples,统一复用公共错误示例定义。

新增默认成功示例 Success_Default,并注册到 components.examples。

将公共错误示例(1400/1401/1404)集中注册到 components.examples,统一响应示例来源。

优化 buildResponseExamples:当 returnExample 为 null、空对象或空数组时,自动引用默认成功示例;当错误示例命中公共定义时,优先使用  引用组件示例,未命中时保持内联示例。

未提供 errorExamples 时,默认引用 #/components/examples/Error_1400,减少重复定义并提升文档一致性。

本次变更不改变接口响应结构,主要改进 OpenAPI 示例复用策略与文档可维护性。
This commit is contained in:
吴天一
2026-02-06 17:07:24 +08:00
parent 45652612b4
commit f479fccf3e

View File

@@ -4,7 +4,7 @@ import { writeFileSync, existsSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { TSchema } from '@sinclair/typebox';
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
import { OneBotAction, ActionExamples } from '@/napcat-onebot/action/OneBotAction';
import { napCatVersion } from 'napcat-common/src/version';
import * as MessageSchemas from '@/napcat-onebot/types/message';
import * as ActionSchemas from '@/napcat-onebot/action/schemas';
@@ -85,6 +85,42 @@ const EmptyDataSchema: JsonObject = {
type: 'null'
};
const DEFAULT_SUCCESS_EXAMPLE_VALUE = {
status: 'ok',
retcode: 0,
data: {},
message: '',
wording: '',
stream: 'normal-action'
} as const;
const DEFAULT_ERROR_EXAMPLE_DEFINITIONS = ActionExamples.Common.errors;
const SUCCESS_DEFAULT_EXAMPLE_KEY = 'Success_Default';
function isObjectRecord (value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
function isEmptyObject (value: unknown): value is Record<string, never> {
return isObjectRecord(value) && Object.keys(value).length === 0;
}
function isEmptyArray (value: unknown): value is [] {
return Array.isArray(value) && value.length === 0;
}
function isMeaninglessSuccessExampleData (value: unknown): boolean {
return value === null || isEmptyObject(value) || isEmptyArray(value);
}
function resolveCommonErrorExampleKey (error: { code: number, description: string; }): string | null {
const matched = DEFAULT_ERROR_EXAMPLE_DEFINITIONS.find(
item => item.code === error.code && item.description === error.description
);
return matched ? `Error_${matched.code}` : null;
}
/* -------------------------------------------------------------------------- */
/* 通用工具函数 */
/* -------------------------------------------------------------------------- */
@@ -472,6 +508,27 @@ export function initSchemas () {
/* -------------------------------------------------------------------------- */
function createOpenAPIDocument (): Record<string, unknown> {
const componentExamples: Record<string, unknown> = {
[SUCCESS_DEFAULT_EXAMPLE_KEY]: {
summary: '成功响应',
value: DEFAULT_SUCCESS_EXAMPLE_VALUE
}
};
DEFAULT_ERROR_EXAMPLE_DEFINITIONS.forEach(error => {
componentExamples[`Error_${error.code}`] = {
summary: error.description,
value: {
status: 'failed',
retcode: error.code,
data: null,
message: error.description,
wording: error.description,
stream: 'normal-action'
}
};
});
return {
openapi: '3.1.0',
info: {
@@ -492,6 +549,7 @@ function createOpenAPIDocument (): Record<string, unknown> {
BaseResponse: BaseResponseSchema,
EmptyData: EmptyDataSchema
},
examples: componentExamples,
responses: {},
securitySchemes: {}
},
@@ -501,47 +559,45 @@ function createOpenAPIDocument (): Record<string, unknown> {
}
function buildResponseExamples (schemas: ActionSchemaInfo): Record<string, unknown> {
const successData = schemas.returnExample ?? {};
const examples: Record<string, any> = {
Success: {
summary: '成功响应',
value: {
status: 'ok',
retcode: 0,
data: schemas.returnExample || {},
message: '',
wording: '',
stream: 'normal-action'
Success: isMeaninglessSuccessExampleData(successData)
? { $ref: `#/components/examples/${SUCCESS_DEFAULT_EXAMPLE_KEY}` }
: {
summary: '成功响应',
value: {
status: 'ok',
retcode: 0,
data: successData,
message: '',
wording: '',
stream: 'normal-action'
}
}
}
};
if (schemas.errorExamples) {
schemas.errorExamples.forEach(error => {
examples[`Error_${error.code}`] = {
summary: error.description,
value: {
status: 'failed',
retcode: error.code,
data: null,
message: error.description,
wording: error.description,
stream: 'normal-action'
}
};
const commonErrorKey = resolveCommonErrorExampleKey(error);
examples[`Error_${error.code}`] = commonErrorKey
? { $ref: `#/components/examples/${commonErrorKey}` }
: {
summary: error.description,
value: {
status: 'failed',
retcode: error.code,
data: null,
message: error.description,
wording: error.description,
stream: 'normal-action'
}
};
});
return examples;
}
examples['Generic_Error'] = {
summary: '通用错误',
value: {
status: 'failed',
retcode: 1400,
data: null,
message: '请求参数错误或业务逻辑执行失败',
wording: '请求参数错误或业务逻辑执行失败',
stream: 'normal-action'
}
$ref: '#/components/examples/Error_1400'
};
return examples;