2021年10月3日日曜日

value-schema v3.1.0をリリースしました

Node.jsとDeno界隈に一大センセーションを巻き起こした(※個人の感想です)データ検証ライブラリーvalue-schemaの新バージョンがリリースされました。

今回の目玉はenumeration()です。名前の通り、TypeScript(Deno含む)でenum等の列挙型を使うときに真価を発揮します。

使いどころ

例えば、ユーザーの状態変更APIのようなものを作るとしましょう。取りうる状態は "active", "inactive", "banned"の3種類の文字列とします。

従来のvalue-schemaでは、以下のようなコードで検証できます。

import vs from "value-schema";

interface User {
	state: string;
};

const user: User = vs.applySchemaObject({
	state: vs.string({
		only: ["active", "inactive", "banned"],
	}),
}, input); // inputは外部からの入力値

もちろんこれでも問題なく動作するんですが、せっかく数種類しかないとわかっているのならTypeScriptの列挙型(enum)を使いたいですよね。こんな感じで書ければ理想です。

import vs from "value-schema";

// 列挙型を定義
enum State {
	ACTIVE = "active",
	INACTIVE = "inactive",
	BANNED = "banned",
};

interface User {
	state: State; // 文字列型ではなくState型として宣言
};

// エラー(文字列型をStateに変換できない)
const user: User = vs.applySchemaObject({
	state: vs.string({
		only: [State.ACTIVE, State.INACTIVE, State.BANNED],
	}),
}, input); // inputは外部からの入力値

でも、applySchemaObject()にとってはstateはあくまで文字列型なので、「文字列型をStateに変換できない」というエラーが発生してしまいます。

そんなときにenumeration()を使うことで、このようにきれいに記述できます。

import vs from "value-schema";

enum State {
	ACTIVE = "active",
	INACTIVE = "inactive",
	BANNED = "banned",
};

interface User {
	state: State;
}

// 今度はOK(State型として認識される)
const user: User = vs.applySchemaObject({
	state: vs.enumeration({
		only: [State.ACTIVE, State.INACTIVE, State.BANNED],
	}),
}, input); // inputは外部からの入力値

何も考えずにstring()enumeration()に変更するだけ。簡単ですね。const enumを使う場合でも書き方は全く同じです。

もちろん数値の列挙型や、文字列と数値が混在した列挙型でも問題なく動作します。

「リテラル型のunion」を使う場合

ところで、TypeScript界隈ではenumではなく「リテラル型のunion」を使おうという風潮があります。詳しくは「さようなら、TypeScript enum」をご覧ください。

enumerate()は、このリテラル型のunionにも対応できます。書き方はenumと同じです。

import vs from "value-schema";

// リテラル型のunion
const State = {
	ACTIVE: "active",
	INACTIVE: "inactive",
	BANNED: "banned",
} as const;
type State = typeof State[keyof typeof State]; // "active" | "inactive" | "banned"

interface User {
	state: State;
}

// これもOK(State型として認識される)
const user: User = vs.applySchemaObject({
	state: vs.enumeration({
		only: [State.ACTIVE, State.INACTIVE, State.BANNED] as const,
	}),
}, input); // inputは外部からの入力値

type State = ...のあたりがちょっと複雑ですが、これはリテラル型のunionを使うときのイディオムみたいなものなので無心で写経してください。

即値を使う場合

あまりないと思いますが、シンボルではなく即値を使いたい場合はちょっとだけ工夫が必要です。

import vs from "value-schema";

// リテラル型のunion
type State = "active" | "inactive" | "banned";

interface User {
	state: State;
}

// これもOK(State型として認識される)
const user: User = vs.applySchemaObject({
	state: vs.enumeration({
		only: ["active", "inactive", "banned"] as const, // constアサーションが必要
	}),
}, input); // inputは外部からの入力値

as constがないと、onlyに渡されるのは文字列配列なので、文字列型をStateに変換できないというエラーが出ます。

注意事項

最後に注意と言うほど大げさなものではないですが注意事項を1つだけ。

onlyプロパティーは必須です

理由は言うまでもないと思いますが、onlyがないと何の値も受け付けないのでバリデーションの意味がないからです。

それでは快適なバリデーションライフを。

0 件のコメント:

コメントを投稿