CatsのValidatedNecを利用してバリデーションを実装してみる

## はじめに
scalaのplayframeworkでValidatedNecを利用してバリデーションを実装しました。
今回はJsonで受け取ったTodoのタイトル、内容をバリデーションして、保存する流れで利用します。


使い所を考えるのが難しいので(Todoアプリでは)、とりあえずAPIで受け取った値をバリデーションします。
(※playframeworkにはデフォルトで[コンビネータ](https://www.playframework.com/documentation/ja/2.4.x/ScalaJsonCombinators)が用意されており、コンビネータでバリデーションできます。
最後にデフォルトで用意されているコンビネータと比較していきたいと思います。)


## JsValue
ValidatedNecは複数のバリデーションエラーメッセージをまとめてチェックしてくれます！
一つのエラーが発生しても他のバリデーションを止めずに実行し、すべてのエラーメッセージを溜め込むことができます！
今の実装だと、条件増えてくると大変そうではありますね。
```scala
case class JsValuePostTodo(
  title:   String,
  content: String
){}

object JsValuePostTodo {
  implicit val todoReads:Reads[JsValuePostTodo] = Json.reads[JsValuePostTodo]

  //バリデーションエラーメッセージ用の型
  type ValidationError = String
  type ValidationResult[A] = ValidatedNec[ValidationError, A]

  // タイトルのバリデーション
  def validateTitle(title: String): ValidationResult[String] = {
    if (title.nonEmpty) title.validNec
    else "Title cannot be empty".invalidNec
  }

  // コンテンツのバリデーション
  def validateContent(content: String): ValidationResult[String] = {
    (
      if (content.nonEmpty) content.validNec else "Content cannot be empty".invalidNec,
      if (content.length >= 2) content.validNec else "content must be at least 2 characters".invalidNec
    ).mapN((_, _) => content)
  }

  // 全体のバリデーション
  def validate(todo: JsValuePostTodo): ValidationResult[JsValuePostTodo] = {
    (
      validateTitle(todo.title),
      validateContent(todo.content)
    ).mapN(JsValuePostTodo.apply)
  }
}

```

バリデーションを順次実行(1つ目のバリデーションが成功した場合のみ次のバリデーション...)↓
```scala
// 全体のバリデーション
def validate(todo: JsValuePostTodo): ValidationResult[JsValuePostTodo] = {
  validateTitle(todo.title) *> validateContent(todo.content).map(_ => todo)
}
```

## Controller
愚直にエラーハンドリングを書くと、コードが冗長になるため、EitherTを使って行っています。(失敗した場合はBadRequestを返し、成功すれば次の処理に進みます。)

```scala
  def post() = Action(parse.json) async { request =>
    (for {
      // リクエスト内のJsonデータを取ってきてJsValueに変換
      jsValuePostTodo <- EitherT.fromEither[Future](request.body.validate[JsValuePostTodo].asEither.leftMap(_ => BadRequest))

      // バリデーション
      _               <- EitherT.fromEither[Future](JsValuePostTodo.validate(jsValuePostTodo).toEither.leftMap( errors => BadRequest(errors.toNonEmptyList.toList.mkString(", "))))

      // Todo保存
      _               <- EitherT.liftF[Future, Result, Option[Todo.Id]](todoRepository.add(jsValuePostTodo.build))
    } yield NoContent).merge
  }
```

`request.body.validate`と`JsValuePostTodo.validate`が並んでいて少し読みにくいですね。
2つ目の`JsValuePostTodo.validate`で、バリデーションが実行されます。

## 用意されているコンビネータとバリデーション
デフォルトで用意されているコンビネータも非常に優秀です。
```scala
implicit val placeReads: Reads[JsValuePostTodo] = (
  (JsPath \ "title").read[String](minLength[String](2)) and
    (JsPath \ "content").read[String](minLength[String](2) keepAnd maxLength[String](10) keepAnd pattern("""^[a-zA-Z]+$""".r, "error.alphanumeric"))
  )(JsValuePostTodo.apply _)
```
ほとんどの場合こっちでバリデーションすると思います。
ただし、複数のフィールド間の相関関係をバリデートする場合、コンビネータでは対応が難しくなるため、そのような場合にはValidatedNecが適していると思います。

## まとめ
エラーメッセージを溜め込む処理がこんなに簡単に！
ドキュメントを見ていると難しそうに見えるかもしれませんが(目が慣れない)、実際に使ってみるととてもシンプルでした😄

## 参考リンク
https://typelevel.org/cats/datatypes/validated.html
https://typelevel.org/cats/datatypes/eithert.html