Scala用のBeanValidationを作った話(Optionとか対応してます)
こんにちは@s_tsukaです。
今日はJavaにあるBeanValidationをscala用に作成しました、という話です。ライブラリは以下のURLに公開しています。Maven Centralにあがっていますので普通に使えるかと思います。
bean-validation-scala/bean-validation-scala - GitHub
BeanValidationについてご存知の方は冒頭の説明を飛ばしてください。
BeanValidationとは(Javaの例)
みなさんはJavaのBeanValidationをご存知でしょうか。JSR303とかJSR349という仕様のアレです。
簡単に言ってしまうとJavaのPOJO、つまり普通のentityオブジェクトを検証するための仕組みです。
ライブラリをビルドパスに加えて、例えば以下のようにアノテーションを付けて、とあるメソッドにオブジェクトを与えると検証してくれる、という感じです。
public class Person {
@Min(0)
private int age;
@NotNull
@Size(min = 1, max = 30)
private String name;
// getter, setter
}
検証結果はエラーの一覧(エラーメッセージのようなもののSet)として提供されます。
上記のアノテーションは何を意味しているかというと、年齢は0以上である必要がある。名前は1文字以上30文字以内である必要がある、ということを表しています。
より詳細な使い方については以下のURLが参考になるかと思います。
JSR 303 Bean Validationで遊んでみるよ! - Yamkazu’s Blog
Javaユーザの方でBeanValidationを使いたい人はhibernateがライブラリを公開しているので、これを使いましょう。
Hibernate Validator - Hibernate Validator
独自のアノテーションを作りたい場合は前に記事を書いていますので、よろしければ参考にしてください。
Bean Validationで独自のアノーテションを作成し、検証を行う - つかびーの技術日記
BeanValidationはScalaでは微妙に使えない(Scalaの例)
さて、便利なBeanValidationですが、scalaの場合は微妙に使えません。もちろん先ほどのPersonクラスのようなcase classを作ってそれをhibernateのvalidatorで検証する場合は正常に動作します。ですが、scala独自の型では正しく動きません。例えば・・・
case class Person(
@(Min@field)(0)
age: Option[Int],
@(Size@field)(min = 1, max = 30)
name: Option[String]
)
このようなケース。これは型がOptionになっています。
これをhibernateのbean-validationで実行すると以下のようなエラーになります。
HV000030: No validator could be found for type: scala.Option<java.lang.String>. javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: scala.Option<java.lang.String>. at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintVali datorManager.verifyResolveWasUnique(ConstraintValidatorManager.java: 218)
Option[String]様のvalidatorが見つからないと言っていますね。そりゃそうだ。hibernateはJava用なのでscalaのOption型に対応したvalidatorを用意しているとは思えません。
今回これをなんとかしよう!と思い作ったライブラリがbean-validation-scalaです。
bean-validation-scala
bean-validation-scala/bean-validation-scala - GitHub
使い方はReadmeに書いてありますので、おそらくリンク先をみればわかると思います。ですが、一応日本語でも解説しておきます。
まずはsbtに依存性を記述します。versionは上記のサイトで最新のものを確認してください。
libraryDependencies ++= Seq(
"com.tsukaby" %% "bean-validation-scala" % "0.3.0"
)
次に実際に使っていきます。例えばこんな感じで
import javax.validation.constraints.Size
import com.tsukaby.bean_validation_scala.ScalaValidatorFactory
import scala.annotation.meta.field
case class Person(
@(Size@field)(min = 1, max = 30)
name: Option[String]
)
object Main {
def main(args: Array[String]): Unit = {
val validator = ScalaValidatorFactory.validator
val invalidBean1 = Person(Some(""))
val violations = validator.validate(invalidBean1)
violations.foreach(x => println(x.getMessage))
val invalidBean2 = Person(Some("1234567890123456789012345678901"))
val violations2 = validator.validate(invalidBean2)
violations2.foreach(x => println(x.getMessage))
// 出力
// size must be between 1 and 30
// size must be between 1 and 30
}
}
ScalaValidatorFactoryでvalidatorを作ります。このvalidatorを使ってvalidate関数を呼び出し、beanを食わせます。
すると、違反があればエラーのSetを返す、という感じです。
上記の場合は1個目のbeanも2個目のbeanもサイズ違反(1文字以上30文字以内ではない)のため、Set内にエラーが入った状態になる、という感じです。
ほとんどhibernateのBeanValidationと同じ、というかそのラッパーライブラリなのですが、OptionやSeqにも対応しているので、普通に使えるかと思います。
今後の方針としては便利なアノテーションを増やしたり、他のScalaのクラスに対応したりなどを考えています。
みなさん、よかったら使ってみてください!
PRやご意見やGithub starもお待ちしております。