完全指南:解析、实现与扩展机制)
Swagger UI 错误转换器Error Transformers完全指南解析、实现与扩展机制【免费下载链接】swagger-uiSwagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.项目地址: https://gitcode.com/GitHub_Trending/sw/swagger-ui本指南系统讲解 Swagger UI 错误子系统中的Error Transformers错误转换器机制它如何通过统一接口把机器生成的原始错误消息改写成对终端用户更有用的提示覆盖输入输出契约、内置转换器实现、删除错误语义、以及如何在钩子hook中注册自己的转换器。读完本文你将掌握 Swagger UI 错误流水线的完整原理并能独立编写、注册、测试自定义错误转换器。什么是 Error TransformersSwagger UI 在解析 OpenAPI/Swagger 规格文件时底层 JSON Schema 校验器会生成大量面向开发者的原始错误例如is not of a type(s) string。这些消息虽然准确但对普通 API 使用者而言不够友好——用户更希望看到“这个字段应该是什么类型”“问题出在哪一行”这类可读信息。Error Transformers 正是为此设计的标准化接口层每个转换器通过transform函数把一组原始错误转换为结构相同但内容更友好的一组错误。它的设计目标非常克制——不改变错误的整体结构只优化其中的信息表达让生成的错误消息对终端用户更有用。在 Swagger UI 的插件体系中err 插件位于 src/core/plugins/err/index.js负责错误状态管理而转换器是其核心处理环节。该插件的完整代码组织如下src/core/plugins/err/ ├── actions.js # 错误相关的 Redux actions ├── index.js # err 插件入口statePlugins.err ├── reducers.js # Reducers内部调用 transformErrors ├── selectors.js # allErrors / lastError 选择器 └── error-transformers/ ├── README.md # 本文档的源头转换器规范 ├── hook.js # transformErrors 钩子注册并串联所有转换器 └── transformers/ ├── not-of-type.js # 内置转换器美化 is not of a type(s) 错误 └── parameter-oneof.js # 内置转换器参数 oneOf 校验错误的定制化当前禁用输入与输出契约transform 函数的标准接口输入Immutable List of Immutable Maps每个转换器的transform函数接收的第一个参数是一个 Immutable List其元素是 Immutable Map。也就是说传入的是 Immutable.js 的List其中每个条目是一个Map代表一条错误记录。例如List([ Map({ path: info.version, message: is not of a type(s) string }), Map({ path: info.license, message: is not of a type(s) object }) ])输出同样形状的 Listtransform函数必须返回同样形态的 List——即由结构相近的 Map 组成的列表。转换器可以改写、增删错误条目但返回的整体容器类型与条目形状应当保持一致以便流水线继续处理。错误来源Redux 错误 actions这些错误源自向 Redux 状态中添加错误的错误 actions。在 src/core/plugins/err/actions.js 中定义了五类错误 actionAction 名称Action Creator用途NEW_THROWN_ERRnewThrownErr(err)新增一条运行时抛出的错误自动serializeErrorNEW_THROWN_ERR_BATCHnewThrownErrBatch(errors)批量新增运行时错误NEW_SPEC_ERRnewSpecErr(err)新增一条规格spec解析错误NEW_SPEC_ERR_BATCHnewSpecErrBatch(errArray)批量新增规格错误NEW_AUTH_ERRnewAuthErr(err)新增一条鉴权错误CLEARclear(filter)按{type: spec}、{source: parser}等条件清除错误CLEAR_BYclearBy(fn)用谓词函数清除错误关键点在于错误在进入 reducer 之前会先经过转换器。以 src/core/plugins/err/reducers.js 为例每次向状态写入错误后都会立即调用transformErrorsimport transformErrors from ./error-transformers/hook [NEW_SPEC_ERR]: (state, { payload }) { let error fromJS(payload) error error.set(type, spec) return state .update(errors, errors (errors || List()).push(fromJS(error)).sortBy(err err.get(line)) ) .update(errors, errors transformErrors(errors)) },也就是说transformErrors是 reducer 写入错误后的必经环节转换发生在“错误进入 Redux state”之前——这正是 README 中所述Errors are transformed before being passed into the reducer的准确含义。必须保留的错误键README 强调当转换器处理完一条错误时必须保证该错误中已有的所有键仍然存在尤其是以下五个键line—— 错误所在的行号level—— 错误级别如error、warningmessage—— 错误消息内容source—— 错误来源如parser、structuraltype—— 错误类型如spec、thrown、auth这些键是错误在 UI 层渲染和排序的基础。在 src/core/components/errors.jsx 中SpecErrorItem会读取source、level、path、line、message来展示来源 级别 位置的错误条目sortedJSErrors也按line排序。因此一个转换器若意外丢掉这些键会导致错误面板渲染异常。另外reducers.js 中的DEFAULT_ERROR_STRUCTURE定义了缺省值let DEFAULT_ERROR_STRUCTURE { line: 0, level: error, message: Unknown error }当 action 负载缺少这些字段时会以默认值补齐再进入转换流程。删除一条错误用 null 覆盖README 提供了一种优雅的删除语义如果想彻底删除某条错误用null覆盖它。转换器返回的数组中null值会在错误返回前被过滤掉。这一语义在 src/core/plugins/err/error-transformers/hook.js 中被双重落实let transformedErrors reduce(errorTransformers, (result, transformer) { try { let newlyTransformedErrors transformer.transform(result, inputs) return newlyTransformedErrors.filter(err !!err) // 过滤被删除的错误 } catch(e) { console.error(Transformer error:, e) return result } }, errors) return transformedErrors .filter(err !!err) // 再次过滤被删除的错误 .map(err { /* ... */ })可以看到每个转换器执行后立即filter(err !!err)剔除null全部转换器串联结束后再次.filter(err !!err)兜底过滤单个转换器若抛出异常会被try/catch捕获并打印Transformer error:日志同时返回未经该转换器修改的原结果保证流水线不因单个转换器故障而中断。这种以null表示删除的设计让转换器在保留 List 形状的同时可以自由表达我要移除这条错误的意图。注册与串联transformErrors 钩子hook.jssrc/core/plugins/err/error-transformers/hook.js 是整个转换机制的装配中心。其核心逻辑用lodash/reduce把所有转换器串联成一个管道import reduce from lodash/reduce import * as NotOfType from ./transformers/not-of-type import * as ParameterOneOf from ./transformers/parameter-oneof const errorTransformers [ NotOfType, ParameterOneOf ] export default function transformErrors (errors) { let inputs { jsSpec: {} // 预留给 spec 上下文见下文说明 } let transformedErrors reduce(errorTransformers, (result, transformer) { try { let newlyTransformedErrors transformer.transform(result, inputs) return newlyTransformedErrors.filter(err !!err) } catch(e) { console.error(Transformer error:, e) return result } }, errors) return transformedErrors .filter(err !!err) .map(err { if(!err.get(line) err.get(path)) { // TODO: re-resolve line number if weve transformed it away } return err }) }值得注意的实现细节第二个参数inputstransform函数可以接收第二个参数inputs。当前hook.js传入的是{ jsSpec: {} }。源码注释明确说明这是一个未实现的遗留物unimplemented artifact——理想情况下jsSpec应指向system.specSelectors.specJS()获取真实规格对象且为兼容 redux4jsSpec应作为参数向下传递而不是在内部调用 store 方法。目前它仅作为占位符供依赖规格上下文的转换器如parameter-oneof使用。错误隔离每个转换器都被try/catch包裹单个转换器抛错不会影响整个错误列表——错误被console.error(Transformer error:, e)记录并回退到转换前的result。行号重解析的 TODO流水线末尾对有path但无line的错误预留了重解析行号的 TODO 分支但目前未实现。要添加自定义转换器只需把新模块导入并追加到errorTransformers数组即可。注意由于 Swagger UI 仓库是只读的你需要在自己项目的 Swagger UI 定制构建或插件中完成这一步而非直接修改仓库源码。内置转换器一not-of-typesrc/core/plugins/err/error-transformers/transformers/not-of-type.js 是第一个内置转换器专门处理 JSON Schema 校验器输出的is not of a type(s)错误。为什么需要它JSON Schema 校验器把当前正在校验的对象称为instance这类原始消息对用户不友好。例如原始消息is not of a type(s) string用户更希望看到的是自然语言化的should be a string实现原理export function transform(errors) { return errors .map(err { let seekStr is not of a type(s) let i err.get(message).indexOf(seekStr) if(i -1) { let types err.get(message).slice(i seekStr.length).split(,) return err.set(message, err.get(message).slice(0, i) makeNewMessage(types)) } else { return err } }) } function makeNewMessage(types) { return types.reduce((p, c, i, arr) { if(i arr.length - 1 arr.length 1) { return p or c } else if(arr[i1] arr.length 2) { return p c , } else if(arr[i1]) { return p c } else { return p c } }, should be a) }算法要点在每条错误的message中查找子串is not of a type(s)若找到把其后按逗号分割的类型列表提取出来保留消息中seekStr之前的前缀可能包含路径等上下文信息用makeNewMessage重新组装一条友好消息规则为单一类型should be a string两个类型should be a string or array三个及以上类型should be a string, array, or number牛津逗号式列举未匹配的消息原样返回不做修改。测试用例佐证单元测试位于 test/unit/core/plugins/err/transformers/not-of-type.js覆盖三种形态// 单个类型 { path: info.version, message: is not of a type(s) string } // → { path: info.version, message: should be a string } // 两个类型 { message: is not of a type(s) string,array } // → { message: should be a string or array } // 三个类型 { message: is not of a type(s) string,array,number } // → { message: should be a string, array, or number }这三个用例分别验证了单数、复数2 个与复数3 个以上类型列表的格式化逻辑同时验证了path等键在转换过程中被原样保留。内置转换器二parameter-oneof当前禁用src/core/plugins/err/error-transformers/transformers/parameter-oneof.js 是第二个内置转换器它把参数对象上模糊的 JSON Schema 错误转换为针对具体关键字in、collectionFormat的可读错误。需要注意的是该转换器当前处于禁用状态——源码第 5 行注释明确写着LOOK HERE THIS TRANSFORMER IS CURRENTLY DISABLED并在transform函数开头直接return errors后续逻辑成为不可达代码文件内以/* eslint-disable no-unreachable */抑制告警。设计意图供参考的规范其设计目标是把如下原始错误is not exactly one from #/definitions/parameter,#/definitions/jsonReference拆解为针对具体字段的定制错误。createTailoredParameterError定义了两种可寻址的检查in关键字取值检查合法的in值集合为[path, query, header, body, formData]定义于VALID_IN_VALUES。若parameter.in不在其中生成错误Wrong value for the in keyword. Expected one of: path, query, header, body, formData.collectionFormat关键字取值检查合法的取值集合为[csv, ssv, tsv, pipes, multi]定义于VALID_COLLECTIONFORMAT_VALUES。若parameter.collectionFormat不在其中生成错误Wrong value for the collectionFormat keyword. Expected one of: csv, ssv, tsv, pipes, multi.生成的每条新错误都遵循契约中的五个键newErrs.push({ message, path: err.get(path) .in, // 精确到出错的子字段 type: spec, source: structural, level: error })若两种检查都不命中则回退返回原错误fall back to making no changes。源码中createTailoredParameterError还依赖第二个参数jsSpec通过get(jsSpec, err.get(path))从规格对象中取出对应的 parameter 定义——这正是 hook.js 传入{ jsSpec: {} }的目的。对应的测试位于 test/unit/core/plugins/err/transformers/parameter-oneof.js但目前以describe.skip挂起与源码的禁用状态一致。若要重新启用需要先解决注释中提到的 flattening problem将单条错误展开为多条子错误的扁平化问题。从 action 到渲染的完整错误流水线综合以上源码一条错误从产生到展示的完整路径是错误 actionnewSpecErr / newThrownErr / ... │ ▼ actions.js 组装 payload补齐 type 等字段 │ ▼ reducers.js 写入 Immutable 状态.push/.concat sortBy line │ ▼ transformErrorshook.js串联执行所有转换器 │ ├─ not-of-type改写 is not of a type(s) 消息 │ ├─ parameter-oneof定制参数错误当前禁用 │ └─ 自定义转换器可扩展 │ └─ 逐级 .filter(err !!err) 剔除 null │ ▼ selectors.jsallErrors / lastError 暴露给 UI │ ▼ errors.jsxerrors-wrapper 渲染按 line 排序支持 Jump to line其中 src/core/plugins/err/selectors.js 提供了两个基于reselect的选择器allErrors—— 返回状态中err.get(errors, List())的全部错误lastError—— 基于allErrors取最后一条。UI 侧src/core/components/errors.jsx 中的Errors组件通过errSelectors.allErrors()拉取错误过滤出thrown类型及level error的错误进行展示并按line排序SpecErrorItem负责渲染spec类型错误显示source level标题、path/line位置和Jump to line跳转链接ThrownErrorItem负责渲染运行时错误。编写自定义 Error Transformer 的实操指南结合 README 契约与 hook.js 的调用方式编写一个自定义转换器的完整步骤1. 导出标准的transform函数每个转换器模块只需导出一个transform函数签名如下export function transform(errors, inputs) { // errors: Immutable List of Immutable Maps // inputs: 上下文对象当前为 { jsSpec: {} } return errors.map(err { // 改写 err 的内容但保留 line / level / message / source / type return err }) }README 给出的示例——把所有行号加 10export function transform(errors) { return errors.map(err { err.line 10 return err }) }2. 遵守错误键契约修改message等字段时务必保留line、level、message、source、type五个键。参考not-of-type.js的做法只err.set(message, ...)其他键不动。3. 用null表达删除若某条错误不再有意义直接return null或把条目置为nullhook 会自动过滤export function transform(errors) { return errors.map(err { if (shouldRemove(err)) { return null // 该错误将被过滤掉 } return err }) }4. 注册到errorTransformers数组在hook.js中import新模块并追加到数组头部或尾部决定其执行顺序import * as MyTransformer from ./transformers/my-transformer const errorTransformers [ NotOfType, ParameterOneOf, MyTransformer ]在你的定制构建中操作当前仓库为只读不应直接改动源码文件。5. 用单元测试验证参照 test/unit/core/plugins/err/transformers/not-of-type.js 的写法用 Immutable 的List/Map构造输入断言transform(ori).toJS()的输出import { Map, List } from immutable import { transform } from path/to/my-transformer describe(my transformer, () { it(should rewrite the message, () { let ori List([ Map({ path: info.version, message: original message }) ]) let res transform(ori).toJS() expect(res).toEqual([{ path: info.version, message: friendlier message }]) }) })6. 遵循健壮性原则参考 hook.js 的容错设计转换器内部建议保持纯函数不产生副作用并在异常时优雅回退生产环境的转换器应避免抛错影响整条错误流水线。小结Error Transformers 是 Swagger UI 错误处理子系统中的一层轻量但关键的抽象统一接口transform(errors, inputs)输入输出均为 Immutable List of Immutable Maps契约清晰、易于测试管道化执行hook.js 用reduce串联所有转换器逐级过滤null并以try/catch隔离故障删除语义用null覆盖即可删除错误无需破坏 List 形状内置范例not-of-type.js 已投产将is not of a type(s)消息美化为自然语言parameter-oneof.js 因扁平化问题暂被禁用其代码可作为设计参考可扩展新增转换器只需三步——实现transform、注册进errorTransformers数组、补充单元测试。理解这层机制后你既可以为自己的 Swagger UI 定制版本编写面向特定场景的友好错误提示也能在阅读 err 插件其他源码actions/reducers/selectors时快速定位转换环节在整体流水线中的位置。【免费下载链接】swagger-uiSwagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.项目地址: https://gitcode.com/GitHub_Trending/sw/swagger-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考