最近Scalaのコードを書いててフレームワークに付随しないタイプのValidationのライブラリなんか無いかなー
と探していたらAccordというものを見つけたので触ってみた備忘録です。
Accord: A sane validation library for Scala
準備
検証用リポジトリはこちらで、使ったライブラリのversionは0.7.2でした。
installはbuild.sbtのlibDependenciesに追加する感じですね
書いたbuild.sbtはこんな感じでした
val commonSettings = Seq( version := "0.1-SNAPSHOT", scalaVersion := "2.12.5", ) lazy val root = (project in file(".")) .settings(commonSettings) .settings( name := "scala validation sample", libraryDependencies ++= serverDependencies ) val serverDependencies = Seq( "com.wix" %% "accord-core" % "0.7.2" )
Accord概要
Accordのvalidationは特定の型に対するValidatorを定義して、
com.wix.accord.validate関数がそのValidatorを利用してvalidationを行い、
最終的にはResult traitを継承した(?)SuccessかFailureを返すみたいな形みたいですね。
validate関数はimplicit parameterでValidatorを渡せるようになっているので、
スコープ内にValidatorを定義しておけば引数から渡さなくても渡せますね。
明示的に渡したい場合は第二引数(?)に渡してあげれば渡せますね。
Ref: http://wix.github.io/accord/api.html#execution
使ってみた
実際に書いてみたコードはこんな感じでした。
APIサーバとかで使うならJSON形式で゚来たRequest Bodyをなんらかのcase classのオブジェクトにマッピングして、
そのオブジェクトをvalidationするみたいな形にするかなーとか思いながら書いてました。
package com.ru.waka import com.wix.accord.{NullSafeValidator, RuleViolation, Validator, validate} import com.wix.accord.ViolationBuilder.singleViolationToFailure import com.wix.accord.dsl._ object Accord { implicit val hogeValidator: Validator[Hoge] = validator[Hoge] { h => h.a is notEmpty h.b is in(1, 2) } implicit val FugaValidator: Validator[Fuga] = validator { f => f.as has size > 0 } implicit val PiyoValidator: Validator[Piyo] = validator { p => p.as is notEmpty p.bs is myNotEmpty as "その値" } /** * @see http://wix.github.io/accord/dsl.html#combinators */ def main(args: Array[String]): Unit = { println(validate(Hoge(a = "", b = 10))) // Failure(Set(a must not be empty, b with value "10" got 10, expected one of: [1, 2])) println(validate(Fuga(Nil))) // Failure(Set(as has size 0, expected more than 0)) println(validate(Fuga(Seq(1, 2, 3)))) // Success println(validate(Piyo(Nil, Nil))) // Failure(Set(as must not be empty, その値 List() は空じゃだめなんじゃ〜)) println(validate(Piyo(Seq("a"), Seq(1)))) // Success } case class Hoge(a: String, b: Int) case class Fuga(as: Seq[Int]) case class Piyo(as: Seq[String], bs: Seq[Int]) def myNotEmpty[T <: Seq[_]]: Validator[ T ] = new NullSafeValidator[ T ]( test = x => x.nonEmpty, failure = x => RuleViolation(x, s"$x は空じゃだめなんじゃ〜") ) }
感想
- 😁 Validationの成功失敗、失敗はどの項目がどのルールで失敗したかが返るので基本的なValidationの機能は満たしてる
- 😁 既存のルールが割と多いので時前で定義せずともわりと使える
- 😁 困ったら自分でも作れるのでまあ便利
- 😁 レポジトリのStar数もそれなりにあるし、開発も続いてるし息短くはなさそう
- 😥 エラーメッセージを日本語にしたいと思ったんだけど、既存のものを変える方法が見つけられてなくて困ってる
h.a as "変な値" is notEmpty
みたいに定義しても変な値 must not be empty
となるのでメッセージ全体は変えられなかった- このあたりはもう少し深掘りしながらissueとかも見ていこうかなと思った