とんちむ日記

RubyとJavaScriptと猫が好きです

javascriptのvalidationをchainする

きっかけ

  • いちいちバリデーションする時にif() ~とかするのがめんどくさい。
  • chainでバリデーションを実行したい
  • Railsのvalidationみたいにerrors.full_messagesみたいにまとめてエラーメッセージ取れたら嬉しい

・・と思ってまたライブラリでも作ろうかと思ってたけど調べていたらvalidator.jsのissueにこんなのがあった

var validator = require('validator');

function ValidatorChain(str) {
    this.str = str;
}

Object.keys(validator).forEach(function (fn) {
    ValidatorChain.prototype[fn] = function () {
        var args = Array.prototype.slice.call(arguments);
        args.unshift(this.str);
        if (!validator[fn].apply(validator, args))
            throw Error(fn + ' failed with ' + this.str);
        return this;
    };
});

exports.check = function (str) {
    return new ValidatorChain(str);
};

check('foo@bar.com').isEmail().isLength(5, 64);

引用元: https://github.com/chriso/validator.js/issues/407#issuecomment-118561692

「様々なユースケースに対応するためにあえて標準ではchainできるようにしてない」 みたいなニュアンスの事が書いてあって、上のコードでchainできるようになる。

とはいえこれだとエラーメッセージはカスタマイズしにくいし、chainを全部通してから全部のエラーメッセージを表示するとかってのもできない。あとクライアントサイドなら別にthrowしなくてメッセージだけでいいって事もあるかもしれない。

と思って上に手を入れてこんな感じにしてみた。

import validator from 'validator'
import messages from './messages.js'

function ValidatorChain(str, messages) {
  this.str = str;
  this.errors = [];
  this.messages = messages;
}

Object.keys(validator).forEach(function (fn) {
  ValidatorChain.prototype[fn] = function (...args) {
    args.unshift(this.str);
    if (!validator[fn].apply(validator, args)) {
      const message = this.messages(fn, args)
      this.errors.push(message)
    }
    return this;
  };
});

function check(str, messages) {
  return new ValidatorChain(str, messages);
}

console.log(check('foo', messages).isEmail().isLength(5, 64).errors);

こうすればmessages.jsをこちらで用意すればメッセージをカスタマイズできるし、全てのバリデーションを通した結果の全エラーメッセージを取得できる。

messages.jsは以下のように作る。args[this.str, ...呼び出したvalidatorの引数]という感じになる。

export default function(fn, args){
  const messages = {
    isEmail: `${args[0]}は正しいEmailの形式ではありません`,
    isLength: `${args[1]}文字から${args[2]}文字以下で入力してください`
  }
  return messages[fn]
}

babelでトランスパイルして実行してみた。

$ node build/index.js
=> [ 'fooは正しいEmailの形式ではありません', '5文字から64文字以下で入力してください' ]

errorメッセージの有無でバリデーションの成否を判断するとか、もしくはValidatorChainのプロパティにエラーがあったかの状態を追加すればいいんじゃないかなと思う。