先回过头来複习一下,在第一章有提到的 defineAPI
里,会有一个 payloadDef
物件,专门用来定义 FinalAPI 的第一个参数 payload
需要哪些属性,其中 {}
物件型别的 payloadDef
,除了能够以 key 定义 payload
需求参数名以外,value 还能够是另一个物件来描述该参数的必填非必填(required
)、验证规则(rules
)、预设值(defaultValue
)以及使用位置(position
),而当中的 required
与 rules
将会与验证引擎息息相关。
参数验证引擎
参数验证引擎是 Karman 内建的一个模组,负责解析 payloadDef
的 rules
与 required
,并在 FinalAPI 发起请求时验证 payload
是否符合规则。
启动验证引擎,需要透过 validation
属性来设定,取决于你要批次或单独启动 FinalAPI 的参数验证,可以被设置在包括 defineKarman
、defineAPI
与 FinalAPI 的第二个参数 config
里,且设置的位置同样会遵循卡门树的继承原则。
可以设定 validation
来开启或关闭验证引擎的地方:
const karman = defineKarman({ // ... validation: true, // 开启此节点以下的所有参数验证 api: { someAction: defineAPI({ // ... validation: false // 关闭这支 FinalAPI 的参数验证 }) }})const [resPromise] = karman.someAction(null, { validation: true // FinalAPI 使用时再一次启用参数验整})// ...
设定验证规则
在 Karman 中,有许多内建的规则可以使用,其中大部分都会自动生成错误讯息,并点明验证失败的参数,除了特别複杂或特殊的规则,不然通常情况下不太会需要使用到客製化的验证函式。
在 payloadDef
中,若想设定参数的验证规则需要透过 rules
属性,但若是想设定参数为必填需要通过 required
,且所有参数预设情况下都会是非必填参数,若是想让参数为必填,即不为 null
或是 undefined
,需要将 required
属性设置为 true
:
const requiredTest = defineAPI({ // ... validation: true, payloadDef: { param01: { required: true } }})requiredTest() // Uncaught ValidationError: Parameter 'param01' is required, but received 'undefined'.
rules
会决定要用甚么规则去验证接收到的参数,以下是 Karman 当中可以使用的验证规则:
字串规则
以字串描述参数的型别,除了基本型别("string"
、"number"
...等)外,也会有一些特殊型别:
"char"
:字符,长度为 1 的字串"int"
:整数"object"
:广义上的物件"object-literal"
:以 {}
构成的物件"array"
:阵列其余内建型别可以参考官方文件。
除了内建的型别外,后续还会介绍 Karman 的一个核心叫「Schema API」,透过这功能订定出来的 schema 也能作为字串规则使用,之后会再详细介绍。
另外,字串规则还支援一种阵列语法,类似其他强型别语言宣告阵列时的语法,如 C# 可以用 int[]
宣告一个整数阵列,使用这个语法作为验证规则时,参数的型别必定是一个阵列,只是可以额外去规範阵列内每个元素的型别,并且还能透过以下的语法,去限制该阵列的长度:
<type>[] # <type> 阵列<type>[<equal>] # 长度 <equal> 的 <type> 阵列<type>[<min>:] # 最小长度 <min> 的 <type> 阵列<type>[:<max>] # 最大长度 <max> 的 <type> 阵列<type>[<min>:<max>] # 最大长度 <max>、最小长度 <min> 的 <type> 阵列
现在,尝试看看以字串规则验证参数,并记得将 validation
设置为 true
来开启验证引擎,另外,这边多配置了个 onError
hooks,并在里头返回了非 undefined
或 null
的值,使下面 FinalAPI 在调用时可以不被错误给中断执行:
关于 hooks 的机制会在后续章节详细说明。
const test = defineAPI({ validation: true, payloadDef: { param01: { rules: "char" }, param02: { rules: "int" }, param03: { rules: "string[]" }, param04: { rules: "char[6]" }, }, onError(err) { console.error(err) return 1 }})test({ param01: "K" }) // validtest({ param01: "Karman" }) // ValidationError: Parameter 'param01' should be 'char' type, but received 'Karman'.test({ param02: 10 }) // validtest({ param02: 0.1 }) // ValidationError: Parameter 'param02' should be 'int' type, but received '0.1'.test({ param03: ["Karman", "Victor"] }) // validtest({ param03: ["Karman", "Victor", 6] }) // ValidationError: Parameter 'param03[2]' should be 'string' type, but received '6'.test({ param04: ["K", "a", "r", "m", "a", "n"] }) // validtest({ param04: ["K", "a", "r", "m", "a", "n", "!"] }) // ValidationError: Parameter 'param04.length' should be equal to '6', but received '7'.test({ param04: "Karman" }) // ValidationError: Parameter 'param04' should be 'array' type, but received 'Karman'.
类别规则
在类别规则中,你可以用一个 class 或建构函式做为验证规则,验证引擎会以 instanceof
对参数进行验证:
const instanceTest = defineAPI({ validation: true, payloadDef: { param01: { rules: Date } }})instanceTest({ param01: new Date(1995, 5, 27) }) // validinstanceTest({ param01: "1995-06-27" }) // Uncaught ValidationError: Parameter 'param01' should be instance of 'Date', but received '1995-06-27'.
自定义验证函式
若是要使用客製化的验证函式,需要使用到 defineCustomValidator
这支函式,它只接受一个参数,即为你要使用的自定义验证逻辑的函式,这函式会接收两个参数,第一个是参数名称,第二个是参数的值,你可以对第二个参数进行验证,不论验证通过与否,皆不须返回任何值,只需在验证未通过时直接抛出错误就好,并且 Karman 有提供了一个 ValidationError
类别,是验证引擎预设抛出的错误类型,你可以藉由抛出此错误来统一所有验证失败时的错误讯息,以便后续的程式判读:
import { defineAPI, defineCustomValidator, ValidationError } from "@vic0627/karman"const sortValidator = defineCustomValidator((param, value) => { if (value !== "asc" || value !== "desc") throw new ValidationError(`Parameter '${param}' must be 'asc' or 'desc' but received '${value}'.`)})const validatorTest = defineAPI({ validation: true, payloadDef: { param01: { rules: sortValidator } }})validatorTest({ param01: "aesc" }) // Uncaught ValidationError: Parameter 'param01' must be 'asc' or 'desc' but received 'aesc'.
正则表达式
以正则表达式做为验证规则时,接受两种规格的参数传入,一种是一般的正则表达式,另一种是一个物件,包含了正则表达式与错误讯息:
const emailRegexp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;const regexpTest = defineAPI({ validation: true, payloadDef: { param01: { rules: emailRegexp }, param02: { rules: { regexp: emailRegexp, errorMessage: "wrong email format" } } }, onError(err) { console.error(err) return 1 }}) const wrongEmail = "karman~"regexpTest({ param01: wrongEmail }) // ValidationError: Parameter 'param01' validation failed, but received 'karman~'.regexpTest({ param02: wrongEmail }) // ValidationError: wrong email format
参数描述符
此种规则主要以物件构成,可以规範包括最大值、最小值、相等值与测量属性等,以下是可用的属性与说明:
min
:最小值,可与 max
一同设定。max
:最大值,可与 min
一同设定。equality
:相等值,优先权最高,若是有设定这属性,min
与 max
将被忽略。measurement
:要以什么属性作为量测单位,预设是 "self"
,也就是测量参数值本身,而其他常见属性有 "length"
、"size"
...等等。验证引擎会先在参数上寻找你所设定的属性,若是找不到,例如 "length"
属性不存在于 number
类型中,这时 Karman 将会丢出警告讯息,并以 undefined
作为后续验证的值,因此,此验证规则通常不会单独使用,而是会配合规则集合设定类型的前置条件,当通过前置规则的验证时,才会进行範围的验证。
const paramDescriptorTest = defineAPI({ validation: true, payloadDef: { param01: { rules: { min: 0, max: 1 } }, param02: { rules: { equality: 5, measurement: "length" } } }, onError(err) { console.error(err) return 1 }})paramDescriptorTest({ param01: 2 })// ValidationError: Parameter 'param01' should be within the range of '0' and '1', but received '2'.paramDescriptorTest({ param02: 2 })// [karman warn] Cannot find property "length" on "2".// ValidationError: Parameter 'param02.length' should be equal to '5', but received 'undefined'.paramDescriptorTest({ param02: "karman!" })// ValidationError: Parameter 'param02.length' should be equal to '5', but received '7'.
规则集合
规则集合,或称複合规则,可一次设定多组规则,依照不同的集合类型,会有不同的通过验证条件,在 Karman 中规则集合会分成下列两项:
联集规则 UnionRules:当参数满足联集规则中的任一项规则,即通过验证。交集规则 IntersectionRules:当参数满足交集规则中的所有规则,即通过验证。而要定义规则集合的方式有两种,第一种是直接在 rules
传入规则的阵列,这会在初始化时自动转换成交集规则,第二种是使用 Karman 提供的 API defineUnionRules
与 defineIntersectionRules
:
import { defineAPI, defineUnionRules, defineIntersectionRules } from "@vic0627/karman"const ruleSetTest = defineAPI({ validation: true, payloadDef: { param01: { rules: ["int", { min: 1 }] }, // param01 与 param02 这两种方法等效 param02: { rules: defineIntersectionRules("int", { min: 1 }) }, param03: { rules: defineUnionRules("string", "number") } }, onError(err) { console.error(err) return 1 }})ruleSetTest({ param01: -1 })// ValidationError: IntersectionRules// [1] Parameter 'param01' should be greater than or equal to '1', but received '-1'ruleSetTest({ param03: false })// ValidationError: UnionRules// [0] Parameter 'param03' should be 'string' type, but received 'false'.// [1] Parameter 'param03' should be 'number' type, but received 'false'.
以上就是参数验证引擎的介绍了,至于下一章节要介绍什么我还需要再思考一下,另外,在撰写这些文章的同时,我也会一边验证套件的实际行为是否符合文章所写内容(应该写测试的),所以更新进度可能会稍微缓慢一些。