Kentaro Kuribayashi's blog

Software Engineering, Management, Books, and Daily Journal.

HTML5のclient-side form validationで書かれたattributeからvalidation rulesを抽出し、client/serverでルールを共通化するモジュールを書いた

Validation Ruleの記述ってけっこう面倒で、うまい方法を思いつけないのでいたのですが、今日、なんとなくHTML5にclient-side form validation specなんてものがあるんだから、それを使ったらいいんじゃないかと思って、ちょっと実装してみました。

以下のようなメリットがあるのではないかと思っています。

  • client-side form validationを使いつつ、server-sideでも同じルールを別の形式で書くのは無駄感。
  • HTML5が、validation rulesをHTMLというフォーマットによって定めているとみなせば、client-side/server-side両方でそれを使えばいい感じになるのでは?オレオレフォーマットをいちいち憶えるより汎用的でいい気がする。
  • デザイナ/プログラマが、同じvalidation rulesを触ることによって、formが受容し得る値の認識が共有できる

id:cho45さんがFormValidator::Simple::Plugin::V8というのを書いて、JavaScript経由でclient/server間のvalidation rules共通化を提示しているのですが、こっちはそのHTML5版という感じですね。

使用例を示します。こういうHTMLがあるとして、普通にclient-side form validationなルールをあれこれ書きます。ここまでは、まあ普通にやるよね、という話。

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>HTML5::ValidationRules</title>
  </head>
  <body>
    <form method="post" action="/post">
      <input type="text" name="text" pattern="[a-zA-Z0-9]+" maxlength="255" />
      <input type="url" name="url" maxlength="255" required />
      <input type="email" name="email" maxlength="255" required="required" />
      <input type="number" name="number" min="200" max="800" />
      <textarea name="textarea" maxlength="1000" required></textarea>
      <input type="submit" value="submit" />
    </form>
  </body>
</html>

そのHTMLから、以下のような感じでvalidation rulesを抽出します。

use HTML::ValidationRules;

my $parser = HTML::ValidationRules->new;
my $rules  = $parser->load_rules(file => 'form.html');

上記のHTMLからは、以下のようなルールが抽出されます。

[
    text     => [ [ HTML_PATTERN => '[a-zA-Z0-9]+' ], [ HTML_MAXLENGTH => 255 ] ],
    url      => [ HTML_URL    => [ HTML_MAXLENGTH => 255 ], 'HTML_REQUIRED'     ],
    email    => [ HTML_EMAIL  => [ HTML_MAXLENGTH => 255 ], 'HTML_REQUIRED'     ],
    number   => [ HTML_NUMBER => [ HTML_MIN => 200 ], [ HTML_MAX => 800 ]       ],
    textarea => [ [ HTML_MAXLENGTH => 1000 ], 'HTML_REQUIRED'                   ],
]

この形式は、FormValidator::Simple->check()にそのままわたせるようになっているので、同梱のFormValidator::Simple::Plugin::HTMLをロードした上で、$rulescheck()にわたしてやるだけです。簡単ですね。

use FormValidator::Simple qw(HTML);

my $query  = CGI->new;
my $result = FormValidator::Simple->check($query => $rules);

いまんとこ、とりあえずコンセプトを示すために簡単にできそうなとこからやってみただけで、仕様への対応状況が全然なので、もう少し対応を進めていきたいと思っています(あと、仕様自体も今日初めて必要そうなとこを読んでみた感じなので、理解が怪しい……)。よろしくどうぞ。

追記

id:tokuhiromさんより以下の通りコメントいただきました。

テンプレートエンジンとかつかってるときにどうするか、がむずかしいところですねー

フォーム要素が静的であれば、HTML::Parserにとって不都合ない限りはだいじょうぶなのかなと思っているのですが、問題はご指摘の場合を含む、動的なルール・要素の増減がある場合で、ざっと以下あたりがあり得るかなと思います。

  1. HTML::ShakanやHTML::FormFuなどのモジュールを使ってフォームを生成している場合
  2. テンプレート内の条件分岐でruleを出し分けしたり、input/textareaを増減たりしている場合
  3. JavaScriptにより、クライアントサイドでruleを出し分けしたり、input/textareaを増減たりしている場合

(1)については、このモジュール的には(とりあえずは)想定していない用途という感じで、(2)(3)についてはちょっとあきらめる感じですかね……。

追々記

上記場合の(1)(2)については、ユーザリクエストに沿ってテンプレートをレンダリングした後に(WAFだったらafter triggerみたいなところで)、$parser->load_rules(html => $string)のインタフェイスを使ってルールを生成し、ユーザのセッションデータあたりに保存しておくという方法で、一応解決可能ではありますね。毎回やるのは遅くなりそうなので、セッションにルールがあったらそれを使い、フォーム生成の条件が変わったらセッションに入れたルールをクリアして新たに生成する、みたいにしたらいいのかな。

追々々記

releaseしました。