JS 參數驗證: Joi 四問
以下內容均可在Joi官方地址參考(文檔略長,英文),本文僅爲個人總結的幾個小疑問。
1. 定義 schema 時 Joi.object.keys() 和 Joi.object() 有什麼區別?
答: 並沒有什麼區別,官方給了三種定義 schema
的方式。如下:
// 使用 { } 來定義
const schema = {
a: Joi.string(),
b: Joi.number()
};
// 使用 Joi.object()
const schema = Joi.object({
a: Joi.string(),
b: Joi.number()
});
// 使用 Joi.object.keys()
const schema = Joi.object().keys({
a: Joi.string(),
b: Joi.number()
});
三種方式實現的效果其實都是一樣的,但是在使用的時候會有一些略微不同,具體如下:
- 當使用 {} 時,只是定義了一個普通的js對象,它不是一個完整的 schema 對象。你可以將它傳遞給驗證方法,但不能調用對象的validate()方法,即類似這種
object.validate()
的操作是不可以的,因爲它只是一個普通的js對象。此外,每次將{}對象傳遞給validate()方法,都將對每個驗證執行一個昂貴的模式編譯操作。 - 當使用 Joi.object() 時,相對於使用 {} ,這是正經的schema 對象,它會在第一次編譯,所以你可以多次將它傳遞給validate()方法,不會增加開銷。另外,你還可以設置 options 來驗證。
- 當使用 Joi.object.keys() 時,其實和使用 Joi.object() 是類似的,但是當你想添加更多的鍵(例如多次調用keys())時,使用joi.object().keys([schema])會更有用。如果只添加一組鍵,則可以跳過keys()方法,直接使用object()。有些人喜歡用keys()來使代碼看起來更加精確(其實這只是一種編程風格)。
2. Joi.validate(value, schema, [options], [callback])中的 options 取值有哪些?
答:options可用的值有如下:
- abortEarly: 設置true,可以在檢測到第一個錯誤時立即返回,默認false(檢查全部)。推薦設置true
- convert:設置true,可以嘗試將值轉換爲所需的類型(例如,將字符串轉換爲數字)。默認爲true。推薦採用默認
- allowunknown: 設置true,則允許對象包含被忽略的未知鍵。默認爲false。推薦設置true
- skipfunctions:如果爲true,則忽略具有函數值的未知鍵。默認爲false。推薦採用默認
- stripunknown: 如果爲true,從對象和數組中刪除未知的元素。默認爲false。也可以特殊的設置成
{ objects: true , arrays: true }
的形式,可以對象和數組分別處理。推薦採用默認 - presence: 設置默認的可選需求。支持的模式:’optional’,’required’,和’forbidden’。默認爲’optional’。推薦採用默認
- escapehtml: 當爲true時,出於安全目的,錯誤消息模板將特殊字符轉義爲html實體。默認爲false。推薦採用默認
- nodefaults:如果爲true,則不應用默認值。默認爲false。推薦採用默認
- context: 提供一個外部數據集用於引用。只能設置爲外部選項來驗證()而不使用any.options()。使用方法:
const schema = Joi.object().keys({
a: Joi.ref('b.c'),
b: {
c: Joi.any()
},
c: Joi.ref('$x')
});
Joi.validate({ a: 5, b: { c: 5 } }, schema, { context: { x: 5 } }, (err, value) => {});
- language: 設置默認的錯誤提示。修改可參考:默認
3. 我需要 promisify Joi.validate 方法嗎?
答: 其實只是兩種寫法,promise和非promise的寫法。首先,Joi.validate() 的寫法很像promise,但是還真不是promise實現的,所以你不用promise的寫法就像這種(官網的這種):
// 場景: 在一個CGI的入口請求參數驗證
const data = { a : '123' };
let schema = Joi.object().keys({
a: Joi.string().required()
});
const {error, query} = Joi.validate(data, schema);
if (error) {
// 需要人工處理異常
console.log(error);
}
使用promise的寫法,就是下面這種,必須要使用 promisify 的,而且強制建議必須要使用 try-catch。
// 場景: 在一個CGI的入口請求參數驗證
const Promise = require('bluebird');
const JoiValidatePromise = Promise.promisify(Joi.validate);
try {
const data = { a : '123' };
let schema = Joi.object().keys({
a: Joi.string().required()
});
const query = await JoiValidatePromise(data, schema);
} catch (error) {
// 使用 catch 捕獲錯誤
console.log(error);
}
兩種寫法都可以,沒有孰好孰壞,不過更推薦第二種寫法,利用try-catch全局捕獲錯誤,另外 Joi 的維護者 目前在實現 async 的寫法, 到時候應該就是直接支持promise了,那就不用promisify了,妙哉。
4. 希望可以有一個包羅萬象的例子?
答:如下:
let testData = { xxx };
let paramSchema = Joi.object().keys({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
access_token: [Joi.string(), Joi.number()],
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email(),
website: Joi.string().uri({
scheme: [
'git',
/git\+https?/
]
}),
search: Joi.string().allow(''),
type: Joi.string().valid('disabled', 'normal', 'all').default('all'),
startTime: Joi.date().min('1-1-1974').max('now'),
endTime: Joi.when( Joi.ref('startTime'), { is: Joi.date().required(), then: Joi.date().max('1-1-2100') } ),
page: Joi.number().integer().min(1).default(1),
pageSize: Joi.number().integer().default(8),
deleteWhenLtTen: Joi.number().integer().max(10).strip(),
arraySelect: Joi.array().items(Joi.string().label('My string').required(), Joi.number().required()),
});
let { error, value } = Joi.validate(testData, paramSchema, { allowUnknown: true, abortEarly: true });
if (error) {
throw error;
}
query = value;
簡單的使用可以看上面,詳細的使用直接看 API
喏,這就是一篇總結文,可能還會繼續增加內容,笑納。