From f479fccf3e277231548faa657800d125821cbe19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=A4=A9=E4=B8=80?= Date: Fri, 6 Feb 2026 17:07:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor(schema):=20=E6=8F=90=E5=8F=96=E5=B9=B6?= =?UTF-8?q?=E5=A4=8D=E7=94=A8=20OpenAPI=20=E5=93=8D=E5=BA=94=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=EF=BC=8C=E5=87=8F=E5=B0=91=E9=87=8D=E5=A4=8D=E5=AE=9A?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 napcat-schema 中引入 ActionExamples,统一复用公共错误示例定义。 新增默认成功示例 Success_Default,并注册到 components.examples。 将公共错误示例(1400/1401/1404)集中注册到 components.examples,统一响应示例来源。 优化 buildResponseExamples:当 returnExample 为 null、空对象或空数组时,自动引用默认成功示例;当错误示例命中公共定义时,优先使用 引用组件示例,未命中时保持内联示例。 未提供 errorExamples 时,默认引用 #/components/examples/Error_1400,减少重复定义并提升文档一致性。 本次变更不改变接口响应结构,主要改进 OpenAPI 示例复用策略与文档可维护性。 --- packages/napcat-schema/index.ts | 118 +++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 31 deletions(-) diff --git a/packages/napcat-schema/index.ts b/packages/napcat-schema/index.ts index a2bf0551..87ca7351 100644 --- a/packages/napcat-schema/index.ts +++ b/packages/napcat-schema/index.ts @@ -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 { + return !!value && typeof value === 'object' && !Array.isArray(value); +} + +function isEmptyObject (value: unknown): value is Record { + 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 { + const componentExamples: Record = { + [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 { BaseResponse: BaseResponseSchema, EmptyData: EmptyDataSchema }, + examples: componentExamples, responses: {}, securitySchemes: {} }, @@ -501,47 +559,45 @@ function createOpenAPIDocument (): Record { } function buildResponseExamples (schemas: ActionSchemaInfo): Record { + const successData = schemas.returnExample ?? {}; const examples: Record = { - 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;