io-tsの基本的な使い方

# io-tsとは
Typescriptで型が合っているかどうかを検査したいときに使用する。
例えば、APIのリクエストパラメータに`isActive`があり、`isActive`には`true`または`false`が入っていることを期待しているとする。
しかし、実際には以下のような値が飛んでくる可能性がある。

- `"true"` → 文字列なのでダメ
- `1` → 数値なのでダメ

これらのパターンを全てのリクエストパラメータに対して検査するのはとても大変である。
しかし、io-tsを使用すれば、こんな感じにスッキリ書くことができる。

```typescript
import * as t from 'io-ts';

const ApiRequest = t.type({
  isActive: t.boolean
});

const validation = ApiRequest.decode(req.body);
```

公式ドキュメントは[こちら](https://github.com/gcanti/io-ts)
[サンプルコード](https://github.com/fukky21/io-ts-sample)も用意したので、よかったらご覧ください。

# 準備
```
$ yarn add io-ts fp-ts
```

`fp-ts`は`io-ts`と依存関係にあるので、一緒にインストールする。
これらの他に、一緒に入れると便利なものを紹介しておく。

- [io-ts-reporters](https://github.com/gillchristian/io-ts-reporters)
    - decodeしたときに出力される文字列をいい感じに整形してくれる
- [io-ts-types](https://github.com/gcanti/io-ts-types)
    - NonEmptyStringなど、便利な型が定義されている(後ほど紹介)

# 基本的な流れ
まずは、io-tsをインポートする

```typescript
import * as t from 'io-ts';
```

次に型を定義する

```typescript
const Book = t.type({
  id: t.number,
  title: t.string,
  author: t.string,
});
```

`t.number`や`t.string`以外にも、`t.boolean`や`t.null`などがある。
詳しくは[こちら](https://github.com/gcanti/io-ts/blob/master/index.md#implemented-types--combinators)を参照。

最後に型が合っているかどうかチェックする

```typescript
const data = {
  id: 1,
  title: 'タイトル',
  author: '著者',
};

const validation = Book.decode(data); // 型をチェック
console.log(validation); // 結果を確認
```

基本的にはこれだけ。非常にシンプル。

# 応用編
`t.string`などの基本的な型だけでなく、例えば、メールアドレスの形式が正しいかどうかなど、もっと厳密に値をチェックしたい場合もあると思う。
ここでは個人的に利用頻度が高いと思った書き方をいくつか紹介する。

## OR
```typescript
name: t.union([ t.string, t.null ])
```

この場合、`name`は`string`でも`null`でもOKということになる。

## 特定の文字列のみ
```typescript
const GenderTypes = {
  male: 'male',
  female: 'female',
  other: 'other',
};

const User = t.type({
  id: t.number,
  name: t.string,
  gender: t.keyof(GenderTypes),
});
```

この場合、`gender`は、`"male"`, `"female"`, `"other"`のいずれかの文字列のみOKということになる。

## NonEmptyString
```typescript
import { NonEmptyString } from 'io-ts-types';

const User = t.type({
  id: t.number,
  name: NonEmptyString,
});
```

`io-ts-types`をインストールすると使える。
`t.string`だと、スペースやタブだけの場合でも通ってしまうので、それらを弾くことができるNonEmptyStringは結構便利。

## 0以上の整数
```typescript
interface IPositiveNumber {
  readonly PositiveNumber: unique symbol
};
const PositiveNumber = t.brand(
  t.number,
  (n): n is t.Branded<number, IPositiveNumber> => 0 < n,
  'PositiveNumber'
);

const User = t.type({
  id: t.number,
  name: t.string,
  age: t.intersection([t.Int, PositiveNumber]),
});
```

`t.number`だと、小数値やマイナス値でも通ってしまうが、この書き方なら`age`は正の整数しか受け付けない。

## メールアドレス形式
```typescript
const REGEX_EMAIL = /^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
interface IEmail {
  readonly Email: unique symbol;
};
const Email = t.brand(
  t.string,
  (input): input is t.Branded<string, IEmail> => REGEX_EMAIL.test(input),
  'Email'
);

const User = t.type({
  id: t.number,
  name: t.string,
  email: Email,
});
```

こちらは正規表現を利用した書き方。パスワードの形式チェックなんかにも使える。

# 注意すべきこと
例えば下のように、パラメータが不足している場合、チェックの段階でキチンと弾かれる。

```typescript
const Book = t.type({
  id: t.number,
  title: t.string,
  author: t.string,
});

// authorが不足している
const data = {
  id: 1,
  title: 'タイトル',
}

const validation = Book.decode(data); // NG
```

ところが、下のようにパラメータが余分な場合は、**チェックが通ってしまうので要注意**

```typescript
const Book = t.type({
  id: t.number,
  title: t.string,
  author: t.string,
});

// priceが余分
const data = {
  id: 1,
  title: 'タイトル',
  author: '著者',
  price: 500,
}

const validation = Book.decode(data); // OK
```