先回過頭來複習一下,在第一章有提到的 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" }) // valid
test({ param01: "Karman" }) // ValidationError: Parameter 'param01' should be 'char' type, but received 'Karman'.
test({ param02: 10 }) // valid
test({ param02: 0.1 }) // ValidationError: Parameter 'param02' should be 'int' type, but received '0.1'.
test({ param03: ["Karman", "Victor"] }) // valid
test({ param03: ["Karman", "Victor", 6] }) // ValidationError: Parameter 'param03[2]' should be 'string' type, but received '6'.
test({ param04: ["K", "a", "r", "m", "a", "n"] }) // valid
test({ 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) }) // valid
instanceTest({ 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 中規則集合會分成下列兩項:
而要定義規則集合的方式有兩種,第一種是直接在 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'.
以上就是參數驗證引擎的介紹了,至於下一章節要介紹什麼我還需要再思考一下,另外,在撰寫這些文章的同時,我也會一邊驗證套件的實際行為是否符合文章所寫內容(應該寫測試的),所以更新進度可能會稍微緩慢一些。