Web API で リクエスト パラメーターなりボディでクライアントからデータを受け取るケースがあります。
その際にリクエストを受け付けてよいか入力データの検証(入力チェックとか呼んだりも)しますが、最近 Validate TypeScript というライブラリを試してみたので ご紹介。
環境
本記事の開発環境は以下となります。
- Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
- Visual Studio Code
- Node.js 8.10.0
- Yarn 1.13.0
- TypeScript 3.3.4000
- validate-typescript 4.0.1
Validate TypeScript 概要
Validate Typescript-https://github.com/Grant-Zietsman/validate-typescriptはスキーマベースのバリデーターで、JSON などのオブジェクトの値や型をチェックすることができる Node.js のライブラリです。MIT ライセンスの元 OSS で公開されています。
豊富な検証ルールをあらかじめ備えているほか、自分で拡張した検証ルールを使うこともできます。
Web API をはじめ、ウェブでリクエストを受け取る機能を作った場合に、クライアントからリクエストしてきた内容を受け入れてよいかチェックする必要があります。
たとえばメールのアドレスは半角の英数字から始まり@
が間にあって、ドメイン名のルールが(実際にはもっと複雑)などと形式があっているかチェックする必要があります。他にもドロップダウンから選択したデータが選択できる範囲の値であるか(不正データを送ってきてないか)や、必須入力ではないけど送ってきたら数値である必要がある、などと単純な型チェックだけでもいろいろとやることがあります。
Validate Typescriptは、その「型チェック」を支援してくるライブラリになります。(逆に言うとメールアドレスが存在するかのチェックや、DB に存在する値との整合性、入力データ間の整合性などはしてくれません。こちらはこちらで必要なことは別途実装する必要があります。)
その「型チェック」ですが色々なやり方があります。各リクエストパラメーターごとにチェックの関数を呼び出したり、リクエストパラメーターに対応するクラスを定義してデコレーター(アノテーション)を付けたり。z
今回Validate Typescriptに着目したのは “スキーマベース” という点で、スキーマをコード内のオブジェクトで渡せるところが良いと思い使ってみることにしました。
こちらのライブラリを使おうとしたところ、インストールエラーが出てしまってプルリクを出して直してもらったことがありました。詳しくは「Validate Typescript に インストールエラーの修正についてのプルリクを送る」の記事になります。こうしてプルリクで改修や機能の提案できるのが OSS のよいところですね。
Validate TypeScript のインストールとサンプルコード
インストールは Node.js プロジェクトで NPM パッケージとして導入します。yarn add validate-typescript
(npm の場合はnpm install validate-typescript
)
公式のサンプルコード抜粋(一部修正)
1 | import { Alias, Email, ID, Optional, Options, RegEx, Type, validate } from 'validate-typescript'; |
前半のzaPhoneNumber
はカスタムの入力検証で南アフリカ共和国の+27
または0
始まりの電話番号チェックです。CustomMessage
はデータ構造の一部をクラスとして表現し、オブジェクトリテラルとの複合を試している感じでしょうか。
const schema
が本題の入力検証でチェックするデータスキーマです。データとして受けとるキー: 入力検証のルール
という形式で記述します。
たとえばid: ID()
は入力データid
というキーにID()
の形式(実態は数値)になっているかをチェックするといった具合です。Type(String)
のような単純な型チェックや、Email()
などの組み込み型、RegEx()
による正規表現のチェックなどがあります。
また必須ではないが受け取った場合はチェックするのOptional()
に、来た場合のチェックを引数にルールを入れる。
具体的な値の選択肢であるOptions()
などもあります。
ひととおりのチェックルールはまかなえています。
サンプルではconst data
で入力データを記述していますが、実際には Web API などを作っているフレームワークなりの入力を受けて、validate(schema, data);
を実行します。
入力検証が通るとvalue
が戻り値で返ってきますが、問題がある場合はエラーがスローされてくるので呼び出すだけでもよいかもしれません。
問題があった場合のエラーの受け取りですが、ValidatorError と AssertionError の再帰構造となっているようです。
入力データの構造が深くなると、それに合わせてchild_errors
という形でエラーも深くなるようになっています。
必要な部分だけを抜粋すると以下のような構造になります。下記はid
の値が数値でなかった場合のエラーです。
1 | { |
property
とvalue
で、キーと受け取った値がわかります。(キーの先頭にドットがついてますが)child_error
のdetails
にエラーメッセージが入っています。英語ですが、そのまま使えそうな感じです。
ただしデータの階層が深い場合は、このデータ構造がchild_errors
という形で深く連なっていくので、そのままでは扱いにくいです。
そのためサンプルに加えて以下のようなコードを追加してフラットに変換しています。
1 | const violations: ValidatorError[] = []; |
JSON をパースする際にreviver 関数を使ってchild_errors
からproperty
が存在する場合に、その値を取得して配列で保持するようにしています。
reviver 関数は JSON をパースする際に値を変換することができるのですが、再帰構造から必要なものだけをうまく引き出せなかったので、横から取り出すようにしました。
これで具体的な入力検証エラーの部分だけを取り出せるたので、必要に応じてレスポンスするためのオブジェクトに詰めなおすことができます。
シンプルなスキーマで、その場に書けるのでコーディングがしやすくきになるらいぶらりなのですが、入力検証エラー時の構造体がちょっと扱いにくいのと、エラーの型定義がサブモジュールのインポートになってしまうため、TSLintを厳しくしていると怒られるという点があり気になりました。
入力検証は似て非なるデータ構造のチェックになり、クラスのデコレータベースは使いにくいと思っているので、スキーマベースという点が気に入っているのですが、エラーのデータ構造がちょっと気になります。
とくに同じキーchild_errors
に入っていてproperty
があるかないかで、末端のエラーが情報か、中間のコンテナーかを判別しないといけないのが、あとからコードを見た時に何をしたいのか分かりにくいだろうなぁと。
もう少し使いやすいものを探してみつつ、時間切れとなったらValidate Typescriptを使わせてもらおうかな。