Schema 校验
Schema 校验是 AIRequestGuard 的开发辅助机制。在 dev 模式下,它会对比 adapter 的输出结果与预期的 ViewModel 结构,提前发现字段缺失、类型不匹配等问题。
仅开发环境
Schema 校验逻辑只在 dev: true 时执行,生产构建中不产生任何运行时开销。
传入 Schema
在调用 AIRequestGuard.register() 时传入 viewSchema 选项,描述期望的 ViewModel 形状(字段名 + 值类型示例):
js
import AIRequestGuard from '@ai-request-guard/core'
export const getUserDetailAdapter = (raw) => {
const dept = raw.dept ?? {}
return {
id: raw.user_id,
userName: raw.username ?? '',
mobile: raw.phone_no ?? '',
deptName: dept.dept_name ?? '未知部门',
age: Number(raw.age ?? 0),
}
}
AIRequestGuard.register({
viewSchema: () => ({
id: 0, // number
userName: '', // string
mobile: '', // string
deptName: '', // string
age: 0, // number
}),
adapter: getUserDetailAdapter,
})ts
import AIRequestGuard from '@ai-request-guard/core'
export const getUserDetailAdapter = (raw: unknown) => {
const r = raw as Record<string, unknown>
const dept = (r.dept ?? {}) as Record<string, unknown>
return {
id: r.user_id as number,
userName: (r.username as string) ?? '',
mobile: (r.phone_no as string) ?? '',
deptName: (dept.dept_name as string) ?? '未知部门',
age: Number(r.age ?? 0),
}
}
AIRequestGuard.register({
viewSchema: () => ({
id: 0, // number
userName: '', // string
mobile: '', // string
deptName: '', // string
age: 0, // number
}),
adapter: getUserDetailAdapter,
})viewSchema 是一个返回对象的函数,对象的值只用来推断期望类型,不作为默认值。写 0 表示期望 number,写 '' 表示期望 string,写 [] 表示期望 array。
三种差异类型
缺失字段(missingFields)
Schema 中定义了但 adapter 输出中不存在的字段。会触发 Console 警告。
[AIRequestGuard] Schema diff detected for "getUserDetailAdapter":
missingFields: ["email", "phone"]通常原因:adapter 尚未映射该字段,或字段名拼写错误。
类型不匹配(typeMismatches)
字段存在但类型与期望不符。会触发 Console 警告。
[AIRequestGuard] Schema diff detected for "getUserDetailAdapter":
typeMismatches: [{ field: "age", expected: "number", actual: "string" }]通常原因:忘记做类型转换(如 Number(r.age))。
多余字段(extraFields)
Adapter 输出了 schema 中未定义的字段。不触发警告,属于 adapter 主动补充的字段,通常是预期行为(如默认值补齐的 avatar)。
查看完整差异报告
配合 Vite 插件使用时,所有 schema diff 会被汇总到 HTML 报告中。详见真实请求拦截。
Schema 定义技巧
js
// 数组类型:值写空数组
AIRequestGuard.register({
viewSchema: () => ({
total: 0,
items: [], // 期望 array 类型
}),
adapter: myAdapter,
})
// 对象类型:值写空对象
AIRequestGuard.register({
viewSchema: () => ({
user: {}, // 期望 object 类型
}),
adapter: myAdapter,
})
// 可以只写关注的字段,不必涵盖所有字段
// adapter 输出中多出的字段会记录为 extraFields 但不报警
AIRequestGuard.register({
viewSchema: () => ({
id: 0,
userName: '',
// 其他字段不写也可以
}),
adapter: myAdapter,
})ts
// 数组类型:值写空数组
AIRequestGuard.register({
viewSchema: () => ({
total: 0,
items: [], // 期望 array 类型
}),
adapter: myAdapter,
})
// 对象类型:值写空对象
AIRequestGuard.register({
viewSchema: () => ({
user: {}, // 期望 object 类型
}),
adapter: myAdapter,
})
// 可以只写关注的字段,不必涵盖所有字段
// adapter 输出中多出的字段会记录为 extraFields 但不报警
AIRequestGuard.register({
viewSchema: () => ({
id: 0,
userName: '',
// 其他字段不写也可以
}),
adapter: myAdapter,
})在测试中断言 Schema
如果你有单元测试,也可以直接使用 validateSchema 做断言:
js
import { validateSchema, hasDiff } from '@ai-request-guard/core'
const viewModel = getUserDetailAdapter(rawData)
const diff = validateSchema('getUserDetailAdapter', viewModel, {
id: 0,
userName: '',
mobile: '',
deptName: '',
age: 0,
})
expect(hasDiff(diff)).toBe(false)
expect(diff.missingFields).toEqual([])
expect(diff.typeMismatches).toEqual([])ts
import { validateSchema, hasDiff } from '@ai-request-guard/core'
const viewModel = getUserDetailAdapter(rawData)
const diff = validateSchema('getUserDetailAdapter', viewModel, {
id: 0,
userName: '',
mobile: '',
deptName: '',
age: 0,
})
expect(hasDiff(diff)).toBe(false)
expect(diff.missingFields).toEqual([])
expect(diff.typeMismatches).toEqual([])