JS 參數驗證: Joi 四問

JS 參數驗證: Joi 四問

以下內容均可在Joi官方地址參考(文檔略長,英文),本文僅爲個人總結的幾個小疑問。

1. 定義 schemaJoi.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

喏,這就是一篇總結文,可能還會繼續增加內容,笑納。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章