Simple, lightweight, flexible validator generator
cafyは、アサーションのようにメソッドチェーンで値のバリデーションを行うライブラリです。 cafyを使えばバリデーションを簡単かつ柔軟に書くことができます。すべてTypeScriptで書かれていて、型定義との相性も抜群です。 Try it out!
たとえばサーバー側で、クライアントから送信されてきたパラメータが正しい形式であるかどうか確認しないと、データベースのエラーやプログラムの例外を引き起こしたりする可能性があります。
「このパラメータはnullやundefinedではない文字列でなくてはならず、1文字以上100文字以下でなくてはならず、a-z0-9の文字種で構成されてなければならない」といった長いバリデーションを、cafyを使えば一行で簡潔に書くことができます。
例外も行うバリデーションごとに用意されているので、ユーザーにわかりやすいエラーメッセージを返すこともできます。
また、バリデータの型文字列を取得する機能があるので、それを使えばドキュメントを生成するときにも役立ちます。
TypeScriptのstrictNullChecks
オプションもサポートしています。
- 軽量 ... 依存関係無し。ブラウザでも使えます
- 簡単 ... 複雑にネストされたオブジェクトも直感的にバリデーションできる
- 柔軟 ... メソッドチェーンで制約を追加したり、独自の型を追加できる
- 強力な型サポート ... 型注釈不要で、バリデータに即した型を取得できる
strictNullChecks
サポートType Guard
サポートAssertion Functions
サポート
Just:
npm install cafy
Happy validation👍
TL;DR
import $ from 'cafy';
const isFruits = $.str.or(['apple', 'banana', 'orange']).ok;
isFruits('apple') // true
isFruits('banana') // true
isFruits('alice') // false
isFruits(42) // false
isFruits(null) // false
まずその値がどんな型でなければならないかを示し、 そのあとに追加の制約をメソッドチェーンで追加していきます。
(以下のドキュメントでは、import $ from 'cafy';
している前提で書いていきます(実際にはcafy関数にどんな名前を付けるかは自由です)。)
たとえば 「それは文字列でなければならない」 という制約を表すにはこう書きます:
$.str
range
メソッドを利用して、さらに 「10文字以上20文字以下でなければならない」 という制約を追加してみます:
$.str.range(10, 20)
実際にバリデーションしてみましょう。
ok
メソッドに検証する値を渡すと、それが条件を満たせばtrue
が返り、そうでなければfalse
が返ります:
$.str.range(10, 20).ok('strawberry pasta') // true
$.str.range(10, 20).ok('alice') // false (短すぎるので)
$.str.range(10, 20).ok('i love strawberry pasta') // false (長すぎるので)
もちろん、上記の例はこのようにまとめられます:
const validate = $.str.range(10, 20).ok;
validate('strawberry pasta') // true
validate('alice') // false (短すぎるので)
validate('i love strawberry pasta') // false (長すぎるので)
cafyは様々な型をサポートしています:
- 文字列 ...
$.str
- 数値 ...
$.num
- 真理値 ...
$.bool
- 配列 ...
$.arr()
- オブジェクト ...
$.obj
- ユーザー定義型 ...
$.type()
- ユニオン ...
$.either()
- リテラル ...
$.literal()
- なんでも ...
$.any
ℹ JavaScriptの仕様上では配列はobjectですが、cafyでは配列はobjectとは見なされません。
後述するように、ユーザー定義型を使えば独自の型を追加することもできます。
それぞれの型がどのようなメソッドを持っているかなどは、APIのセクションをご確認ください。
cafyは、デフォルトでnull
もundefined
も許容しません。
null
やundefined
を許容したい場合は、これらのオプションを使用します:
デフォルトでundefined
はエラーになります:
$.str.ok(undefined) // false
undefined
を許容する場合はoptional
を型の前にプリフィクスします:
$.optional.str.ok(undefined) // true
デフォルトでnull
はエラーになります:
$.str.ok(null) // false
null
を許容する場合はnullable
を型の前にプリフィクスします:
$.nullable.str.ok(null) // true
nullable
とoptional
は併用できます:
$.nullable.optional.str...
$.optional.nullable.str...
$.optionalNullable.str...
undefined | null | |
---|---|---|
(default) | x | x |
optional |
o | x |
nullable |
x | o |
optional + nullable |
o | o |
cafyの実体はContext
クラスです。そして、cafyで実装されている全ての型はContext
クラスを継承したクラスです。
従って、Context
クラスにある次のメソッドおよびプロパティは全ての型で利用可能です。
テスト対象の値とテスト結果のペア(配列)を取得します。
バリデーションを実行します。
合格した場合はfalse
で、そうでない場合はtrue
です。
.ok()
の否定です。
(nok は not ok の略です)
バリデーションを実行します。
合格した場合はtrue
で、そうでない場合はfalse
です。
.test() == null
と同義です。
ℹ TypeScriptを使っているなら、このメソッドの結果で分岐を行うことで、以後対象の変数の型を推論することができます(Type Guard)。これについては、後述の「TypeScriptとの親和性」で詳しく説明します。
カスタムのバリデーションを実行できます。
引数の関数がtrue
を返すと妥当ということになり、false
またはError
を返すと不正な値とします。
$.str.pipe(x => x.indexOf('alice') == -1).ok('strawberry pasta') // true
$.arr().pipe(x => x[1] != 'b').ok(['a', 'b', 'c']) // false
値が
null
またはundefined
のときはpipe
は実行されないため、pipe
内でnullチェックする必要はありません。
バリデーションを実行します。
不合格の場合はError
をthrowします。
ℹ
TypeScriptを使っているなら、このメソッドを呼び出すことで、以後対象の変数の型を推論することができます。これについては、後述の「TypeScriptとの親和性」で詳しく説明します。現在正しく動作しません。詳しくは: microsoft/TypeScript#34596
バリデーションを実行します。
合格した場合はnull
で、そうでない場合はError
です。
バリデーションを実行します。
合格した場合は値を返し、そうでない場合はError
をthrowします。
このインスタンスの型を表す文字列を取得します。
型 | |
---|---|
$.str |
string |
$.optional.str |
string? |
$.nullable.str |
(string | null) |
$.optional.nullable.str |
(string | null)? |
$.arr($.str) |
string[] |
$.either($.str, $.num) |
(string | number) |
optional
か否か(読み取り専用)
nullable
か否か(読み取り専用)
.any
Anyバリデータを使うと、「undefinedやnullはダメだけど、型は何でもいい」といった値を検証したいときに便利です:
$.any.ok('strawberry pasta') // true
Any固有のメソッドはありません。
.arr(query)
.array(query)
配列をバリデーションしたいときはこのバリデータを使用します。
配列の各々の要素に対してバリデーションを定義できます:
$.arr($.num) // 数値の配列でなければならない
$.arr($.str.min(10)) // 10文字以上の文字列の配列でなければならない
もちろんarrayを入れ子にもできます:
$.arr($.arr($.num)) // 「数値の配列」の配列でなければならない
$.arr($.arr($.str.min(10))) // 「10文字以上の文字列の配列」の配列でなければならない
要素の数がthreshold
以上でなければならないという制約を追加します。
要素の数がthreshold
以下でなければならないという制約を追加します。
min
以上max
以下の数の要素を持っていなければならないという制約を追加します。
$.arr().range(2, 5).ok(['a', 'b', 'c']) // true
$.arr().range(2, 5).ok(['a', 'b', 'c', 'd', 'e', 'f']) // false
$.arr().range(2, 5).ok(['a']) // false
ℹ️ range(30, 50)
はmin(30).max(50)
と同義です。
要素の数がlength
でなければならないという制約を追加します。
ユニークな配列(=重複した値を持っていない)でなければならないという制約を追加します。
$.arr().unique().ok(['a', 'b', 'c']) // true
$.arr().unique().ok(['a', 'b', 'c', 'b']) // false
特定のインデックスの要素に対してカスタムのバリデーションを実行できます。
引数の関数がtrue
を返すと妥当ということになり、false
またはError
を返すと不正な値とします。
引数にはcafyインスタンスも渡せます。
$.arr().item(1, $.num).ok(['a', 42, 'c']) // true
$.arr().item(1, $.num).ok(['a', 'b', 'c']) // false
各要素に対してカスタムのバリデーションを実行できます。
引数の関数がtrue
を返すと妥当ということになり、false
またはError
を返すと不正な値とします。
引数にはcafyインスタンスも渡せます。
$.arr().each(x => x < 4).ok([1, 2, 3]) // true
$.arr().each(x => x < 4).ok([1, 4, 3]) // false
.bool
.boolean
真理値(true
かfalse
)をバリデーションしたいときはこのバリデータを使用します。
固有のメソッドはありません。
.num
.number
数値をバリデーションしたいときはこのバリデータを使用します。
整数でなければならないという制約を追加します。
$.num.int().ok(0) // true
$.num.int().ok(1) // true
$.num.int().ok(-100) // true
$.num.int().ok(0.1) // false
$.num.int().ok(Math.PI) // false
$.num.int().ok(NaN) // false
$.num.int().ok(Infinity) // false
threshold
以上の数値でなければならないという制約を追加します。
threshold
以下の数値でなければならないという制約を追加します。
min
以上max
以下の数値でなければならないという制約を追加します。
ℹ️ range(30, 50)
はmin(30).max(50)
と同義です。
.obj(props)
.object(props)
オブジェクトをバリデーションしたいときはこのバリデータを使用します。
引数にプロパティの定義を与えて、複雑なオブジェクトも簡単にバリデーションできます。
例えば次のようなオブジェクトをバリデーションしたいとします:
const x = {
some: {
strawberry: 'pasta',
alice: false,
tachibana: {
bwh: [68, 52, 67]
}
},
thing: 42
};
バリデータはこのように定義できます:
$.obj({
some: $.obj({
strawberry: $.str,
alice: $.bool,
tachibana: $.obj({
bwh: $.arr($.num)
})
}),
thing: $.num
}).ok(x) // true
この型では、エラーに次のプロパティが含まれています:
prop
... バリデーションに不合格になったプロパティ名path
... 不合格になった子のプロパティまでのパスerror
... エラー内容
例えば次のような検証を行った時、エラーは次のようになります:
$.obj({
x: $.obj({
y: $.obj({
z: $.num
})
})
}).test({
x: {
y: {
z: 'foo'
}
}
});
Thrown:
{ Error: x.y.z: must-be-a-number
at ...
path: [ 'x', 'y', 'z' ],
error:
Error: must-be-a-number
at ... }
引数のプロパティ定義で言及した以外のプロパティを持っている場合にエラーにします。
デフォルト:
$.obj({ foo: $.num }).ok({ foo: 42, bar: 24 }) // true
strict:
$.obj({ foo: $.num }).strict().ok({ foo: 42, bar: 24 }) // false
.str
.string
文字列をバリデーションしたいときはこのバリデータを使用します。
与えられた正規表現とマッチしていなければならないという制約を追加します。
$.str.match(/^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/).ok('2017-03-07') // true
match
の否定。
与えられたパターン内の文字列のいずれかでなければならないという制約を追加します。
pattern
は文字列の配列または|
で区切られた文字列です。
$.str.or(['strawberry', 'pasta']).ok('strawberry') // true
$.str.or(['strawberry', 'pasta']).ok('alice') // false
$.str.or('strawberry|pasta').ok('pasta') // true
引数に与えられた文字列を含んでいてはならないという制約を追加します。
$.str.notInclude('fuck').ok('She is fucking rich.') // false
$.str.notInclude(['strawberry', 'alice']).ok('strawberry pasta') // false
threshold
以上の文字数でなければならないという制約を追加します。
threshold
以下の文字数でなければならないという制約を追加します。
min
以上max
以下の文字数でなければならないという制約を追加します。
ℹ️ range(30, 50)
はmin(30).max(50)
と同義です。
文字数がlength
でなければならないという制約を追加します。
.either(queryA, queryB)
「文字列または数値」とか「真理値または真理値の配列」のようなバリデーションを行いたいときは、either
バリデータを使うことができます。
例:
// 文字列または数値
$.either($.str, $.num).ok(42) // true
either
を任意の数入れ子にする事で実現できます:
// 文字列または数値または真理値
$.either($.str, $.either($.num, $.bool)).ok(42) // true
.literal(literal)
特定の値であることを保証するバリデーションを行いたいときは、literal
バリデータを使うことができます。
例:
// 文字列'foo'でなければならない
$.literal('foo').ok('foo') // true
TypeScriptで使うときに便利です。
.use(query)
既存のContextを拡張したいときに使います。
const other = $.str;
$.optional.use(other).ok(undefined) // true
$.nullable.use(other).ok(null) // true
.type(type)
cafyで標準で用意されているstring
やnumber
等の基本的な型以外にも、ユーザーが型を登録してバリデーションすることができます。
型を定義するには、まずcafyのContext
クラスを継承したContextクラスを作ります。
TypeScriptでの例:
import $, { Context } from 'cafy';
// あなたのクラス
class Foo {
bar: number;
}
// あなたのクラスを検証するための、cafyのContextクラスを継承したクラス
class FooContext<Maybe = Foo> extends Context<Foo | Maybe> {
// 型の名前
public readonly name = 'Foo';
constructor(optional = false, nullable = false) {
// ✨おまじない✨
super(optional, nullable);
// 値が Foo のインスタンスであるかチェック
this.push(v => v instanceof Foo);
}
//#region ✨もっとおまじない✨
public makeOptional(): FooContext<undefined> {
return new FooContext(true, false);
}
public makeNullable(): FooContext<null> {
return new FooContext(false, true);
}
public makeOptionalNullable(): FooContext<undefined | null> {
return new FooContext(true, true);
}
//#endregion
}
バリデーションするときは、type
メソッドにクラスを渡します:
$.type(FooContext).ok(new Foo()); // true
$.type(FooContext).ok('abc'); // false
また、Context
を継承するクラスにメソッドを実装することで、Context中でそのメソッドを利用することもできます。
例として、上述のFooContext
に、「プロパティbar
が指定された値以上でなければならない」という制約を追加するメソッドmin
を定義してみましょう:
class FooContext<Maybe = Foo> extends Context<Foo | Maybe> {
...
public min(threshold: number) {
this.push(v => v.bar >= threshold);
return this;
}
}
return this;
しているのは、メソッドチェーンできるようにするためです。
このメソッドを使う例:
const foo = new Foo();
foo.bar = 42;
$.type(FooContext).min(40).ok(foo); // true
$.type(FooContext).min(48).ok(foo); // false
cafyはTypeScriptで書かれているため、強力な型定義を持ち、バリデーションに応じて変数の型を推論し、それ以降のフローで型を絞り込むことができます。
例えば、「x
は文字列でなければならない」とバリデーションした後のx
の型は明らかに文字列です(バリデータの実装にミスが無いと仮定した場合)。
ok
メソッドは型定義においてTypeScriptのType Guardを実装しており、ok
メソッドの返り値を使って条件分岐を行うと、そのスコープではバリデーションした変数の型が正しいものに絞り込まれます(ナローイング)。これは、ok
メソッドにバリデーションに合格しない値を渡すとfalse
が返り分岐が実行されないことが判るので、分岐先スコープの変数の型は必ず求めている型になることが保証されるからです。
例:
const x = 42 as unknown;
// この時点でxの型は unknown
if ($.str.ok(x)) {
x;
// ↑この時点でxの型は string
// この例ではxはnumberなので、実際にはここに到達することはない
}
次のように書いても同じです:
function something(x: unknown) {
// この時点でxの型は unknown
if (!$.str.ok(x)) return;
x;
// ↑この時点でxの型は string
}
詳しくはTypeScriptのType Guardのドキュメントを参照してください。
また、TypeScript 3.7で導入されたAssertion Functionsもサポートしていて、
assert
メソッドにある変数を渡して呼び出すと、その後の変数の型はバリデーションされた型になります。これは、assert
メソッドにバリデーションに合格しない値を渡すと、即座に例外がthrowされるので、
その後の変数の型は必ず求めている型になることが保証されるからです。TypeScript 3.7のAssertion Functionsによりこれを推論することが可能になります。例:
const x = 42 as unknown;
// この時点でxの型は unknown
$.str.assert(x);
x;
// ↑この時点でxの型は string
// この例ではxはnumberなので、実際にはここに到達することはない
詳しくはTypeScriptのAssertion Functionsのドキュメントを参照してください。
cafyの$.literal()
バリデータは、TypeScriptのconst assertionを使ったときのように型が値そのものになります。例:
if ($.literal('foo').ok(x)) {
x;
// ↑xの型は 'foo' (stringではなく)
}
if ($.either($.literal('foo'), $.literal('bar')).ok(x)) {
x;
// ↑xの型は 'foo' | 'bar'
}
配列、オブジェクト、ユニオン型といった複雑な型も、正しく推論することができます。 いくつかバリデーション後の型がどうなるのかの例を示します:
const b = $.arr($.num).get(foo)[0];
// ↑ b の型は number[]
const c = $.either($.str, $.num).get(foo)[0];
// ↑ c の型は string | number
const d = $.obj({
foo: $.obj({
bar: $.obj({
baz: $.num
}),
qux: $.arr($.arr($.bool))
})
}).get(foo)[0];
/* ↑ d の型は:
{
foo: {
bar: {
baz: number;
};
qux: boolean[][];
};
}
*/
cafyはTypeScriptのstrictNullChecks
をサポートしていて、型定義においてnull
、undefined
、またはそうでないかを区別できます。例:
const a = $.str.get(foo)[0]; // a の型は string
const b = $.optional.str.get(foo)[0]; // b の型は string | undefined
const c = $.nullable.str.get(foo)[0]; // c の型は string | null
const d = $.optional.nullable.str.get(foo)[0]; // d の型は string | undefined | null
Please see ChangeLog!